/*
 * Created on Oct 17, 2003
 *
 * To change the template for this generated file go to
 * Window&gt;Preferences&gt;Java&gt;Code Generation&gt;Code and Comments
 */
package auditoryUIbk;

import javax.swing.*;

import java.awt.*;
import java.awt.geom.*;
import java.awt.image.BufferedImage;
import java.awt.event.*;
import java.util.*;
import java.io.*;

import auditoryTree.*;
import experiment.*;
import fr.emn.utils.color.ColorUtils;

/**
 * @author sszhao
 *
 * To change the template for this generated type comment go to
 * Window&gt;Preferences&gt;Java&gt;Code Generation&gt;Code and Comments
 */
@SuppressWarnings("unchecked")
public class SimpleStrokeAudioUIbk extends JPanel {

	// Debug parameters. Everything should be false during the experiment
	public static final boolean DEBUG = true; // now this will also show visual debug window
	public static final boolean DEMO_MODE = false; //setup a demo mode to demostrate the technique to people
	public static final boolean SUMMARY_ONLY = false;
	public static final boolean SKIP_AUDIO_LOAD = false;

	public static final boolean AUTO_REPEAT = true;
	
	public static final boolean ALLOW_TOUCHPAD_SIDEBUTTON = true;
	public static final boolean ALLOW_SPACEBAR = (DEMO_MODE==true)?false:true;
	
	/** Where the first pie slice is rendered. Currently North.  */
	public static final double DEFAULT_START = Math.PI / 2;
	public static final Font IPOD_FONT = new Font("Helvetica", Font.BOLD, 16);
	public static final double DRAG_REACTION_SENSITIVITY = 100;
	
	public static enum TextPosition {NORTH, SOUTH, EAST, WEST, CENTER};
	
	static final long serialVersionUID = 101; 

	//================================================
	//Variables to store the menu tree and menu items
	//================================================
	ArrayList moFullList;
	TreeModel moList; // A treemodel of of menu items
	TreeModel moMarkingMenu; //A treemodel for the marking menu section
	public TreeAudibleModel moGraphicModel;
	
	//the last time selected 
	static AudibleMenuItem moLastItem;

	//==============================================
	//system mode related variables
	//==============================================
	//Modes for the system
	private static short CONTENT_MODE = 10;
	//private static short COMMAND_MODE = 11;
	private static short CONTINUOUS_MODE = 12;
	
	//we have two modes for browsing through long lists
	//if spiral, maximum 8 per circle is allowed 
	//if compact, a circle can have as many items as possible
	//These 4 are command mode
	private static final short SPIRAL_MODE = 40;
	private static final short COMPACT_MODE = 41;
	
	//both earpod mode ,and speedzoom mode seems to fit into compact mode right now
	//but not sure if that works or not
	private static final short EARPOD_MODE = 42;  
	private static final short SPEEDZOOM_MODE = 43; 
	
	private static final short ORTHOZOOM_MODE = 44;
	private static final short PULLDOWN_MODE = 45;
	
	///multi_stroke and continous_stroke modes are not used right now
	private static final short MULTI_STROKE_MODE = 46; 
	private static final short CONTINUOUS_STROKE_MODE = 47;

	private static short CurrentMode = COMPACT_MODE; //SPIRAL_MODE; //MULTI_STROKE_MODE;//CONTINUOUS_STROKE_MODE;

	//Status for the command mode 
	private static final short C_INIT_COMMAND = 30;
	private static final short C_START_SELECTION = 31;
	private static final short C_BETWEEN_SELECTION = 32;
	private static final short C_FINISH_COMMAND = 33;

	private static short CurrentStatus = C_INIT_COMMAND;

	private static final short FAST_STATE = 51;
	private static final short SLOW_STATE = 52;
	
	///these two states are used to handle the speed dependent zooming situation
	private static short CurrentScrollingState = SLOW_STATE;
	private static short LastScrollingState = SLOW_STATE;
	
	private static boolean PlayCommands = false;
	
	private static int SPIRAL_ITEM_LIMIT = 12;

	//========================================================
	// Touchpad related variables
	//========================================================
	
	//	orientation fields
	private static final int NORTH = 0;
	private static final int EAST = 1;
	private static final int SOUTH = 2;
	private static final int WEST = 3;

	private static final int TOUCHPAD_ORIENTATION = EAST;
	
	//creating four values that the systems will read. 
	static int _rawx, _rawy, _rawz, _x, _y, _z, _button, _lastZ, _lastEvent, _x0, _y0;
	
	static {
		try {
			System.loadLibrary("simpletpinfoJava");
		} catch (UnsatisfiedLinkError ule) {
			System.out.println(ule);
			System.exit(0);
		}
	}

	//native methods to check the touchpad coordinates 
	public static native void initTouchPad();
	public static native void terminateTouchPad();
	public static native void readTouchPadValues(int[] values);
	
	//	Timer to check for touchpad positions from the C++ dll
	java.util.Timer fingerDetector = new java.util.Timer();
	//	for sensing the signals
	static final int miDelay = 100;
	//	 repeat every sec.
	static final int miPeriod = 20;
	
	//========================================================
	//Screen feedback related variables
	//========================================================
	static final short PAINT_STROKE = 20;
	static final short PAINT_STROKE_COMPONENT = 21;
	static final short CLEAR_SCREEN = 22;

	static short PaintStatus = CLEAR_SCREEN;

	private Gesture moGesture;

	//=============================================
	//Screen position related variables
	//===============================================
	
	//	screen width and height
	public static final int DEBUG_WIDTH = 400; //1280;
	public static final int DEBUG_HEIGHT = 700; //1024;
	public static final int VF_WIDTH = 250; //visual frame width
	public static final int VF_HEIGHT = 320; //visual frame height
	
	public static final int DEBUG_RADIUS = DEBUG_WIDTH / 2 - 20;; //radius needs to be smaller than half of the shorter one of
	public static final double DEBUG_ZOOM = 0.5;
	//screen width or height 

	//These values are calculated based on the circle I put on the touchpad.
	//private static final int TP_HMAX = 1510; //the greatest x value possibly received using a finger at the north orientation 2047
	//private static final int TP_HMIN = 470; //the smallest x value possibly received using a finger at the north orientation
	//private static final int TP_VMAX = 1360; //the greatest y value possibly received using a finger at the north orientation 1535
	//private static final int TP_VMIN = 210; //the smallest y value possibly received using a finger at the north orientation

	private static int TP_HMAX;
	private static int TP_HMIN;
	private static int TP_VMAX;
	private static int TP_VMIN;

	//values for toronto touchpad
    private static final int TORONTO_TP_HMIN = 550; //the smallest x value possibly received using a finger at the north orientation
	private static final int TORONTO_TP_HMAX = 1560; //the greatest x value possibly received using a finger at the north orientation 2047
	private static final int TORONTO_TP_VMIN = 210; //the smallest y value possibly received using a finger at the north orientation
	private static final int TORONTO_TP_VMAX = 1330; //the greatest y value possibly received using a finger at the north orientation 1535
	
	//value for redmond touchpad
	private static final int REDMOND_TP_HMAX = 1640; //the greatest x value possibly received using a finger at the north orientation 2047
	private static final int REDMOND_TP_HMIN = 600; //the smallest x value possibly received using a finger at the north orientation
	private static final int REDMOND_TP_VMAX = 1240; //the greatest y value possibly received using a finger at the north orientation 1535
	private static final int REDMOND_TP_VMIN = 240; //the smallest y value possibly received using a finger at the north orientation
	
	//the y value will change between the slider_min to slider_max here. 
	//slider is no longer used. 
	//private static final int SLIDER_MIN = 100;
	//private static final int SLIDER_MAX = 1500;

	//  --------------------
	//        -----  (the end of the radius)
	//        -----  (outer zone threshold)
	//        -----  (inner zone threshold) 
	//   / / /     \ \ \
	//  | | |  (0,0)| | |
	//   \ \ \     / / / 
	//        -----
	//        -----     (this is the touchpad) 
	//        -----
	//-----------------------
	//so the outer zone is max to max - 50
	private static final double OUTER_ZONE_THRESHOLD = DEBUG_RADIUS - 110; //toronto value is 100
	//so the inner zone is 0 - 50;
	private static final double INNER_ZONE_THRESHOLD = 30;
	
	//private static double TRIGGER_DISTANCE = 15.0;
	
	//============================================
	//sound prefix and suffix
	//============================================
	//private static final String SOUND_PREFIX = ".\\ExperimentAudio\\andrewaudio";

	//private static final String NO_SOUND_PREFIX = ".\\ExperimentAudio\\andrewaudio\\number";

	private static final String SOUND_SUFFIX = "wav";
	
	// 1 ** JASS sound configuration options
    // The audio API used: javasound, directx, rtaudio or asio.
    // Test between javasound or directx. directx requires rtaudio.dll
    static String soundAPI = "javasound";
    
    // JASS's working buffer size.
    // Low values give less lag but may produce audio side effects.
    // Test and use the lowest possible value.
    static int bufferSize = DEBUG?1024:512;
    
    // Another buffer size. I don't know what it is but keep it low.
    static int bufferSizeRender = DEBUG?4096:2048;
    
    // Working sample rate. Best when it matches file sample rates.
    // oversampling is bugged.
    static float sampleRate = 22050;
	 // This is the delay in seconds for playing the spoken option sounds.
    // This allows slightly separating "wheel clicks" from spoken audio.
    static float optiondelay = 0.1f;
    
    static int PlayingChannels = 5;  //this will make all option sounds interruptible
    
    // This is the set of UI (clicks) audio files.
    static String[] UIFiles = new String[] {
    	 "/ui/clickwheel01",
    	 "/ui/browseTop",
       	 "/ui/browseBottom"
    };
    
   
    static String[] UIFiles2 = new String[] {
   	 "/ui/press",
   	 "/ui/release",
   	 "/ui/clickwheel03",
   	 "/ui/now",
   	 "/ui/browseBottom", // for debug
   	 "/ui/futurebeep",    // for debug
   	 "/ui/browseBottom"
   };
    
    //these int here are used to play the soundset for command audio 
    //don't confuse them with experiment status. (bad design here) 
    static final int INIT_COMMAND = 0;
    static final int YOU_SELECTED = 1;
    static final int LEAF_NODE = 2;
    static final int EXPERIMENT_INSTRUCTION = 3;
    static final int TRIAL_ENDED = 4;
    
    //there are several voices 
    static final String PAULA = "paulaaudio";
    static final String ANDREW = "andrewaudio";
    static final String LONGLIST = "longlistaudio\\";
    
    //static names for events which we will log into the detail log
    static final String TOUCH_EVENT = "touch";
    static final String DRAG_EVENT = "drag";
    static final String RELEASE_EVENT = "release";
    
    static String[] Commands = new String[] {
			//add the experiment related commands
			getPrefix(ANDREW) + "\\command\\experimentInstruction2.wav",
			getPrefix(ANDREW) + "\\command\\conditionstart.wav",
			getPrefix(ANDREW) + "\\command\\press-first.wav",
			getPrefix(ANDREW) + "\\command\\experimentend.wav",
			getPrefix(ANDREW) + "\\command\\conditionend.wav",
			getPrefix(ANDREW) + "\\command\\break-5mn.wav",
			getPrefix(ANDREW) + "\\command\\continueread.wav",
			getPrefix(ANDREW) + "\\command\\oninstructionsheet.wav",
			getPrefix(ANDREW) + "\\command\\askinstruction.wav",
			getPrefix(ANDREW) + "\\command\\pleaseread.wav"
    };
    
    static String[] Numbers = new String[] {
    		getPrefix(ANDREW + "\\number") + "\\0.wav",
    		getPrefix(ANDREW + "\\number") + "\\1.wav",
    		getPrefix(ANDREW + "\\number") + "\\2.wav",
    		getPrefix(ANDREW + "\\number") + "\\3.wav",
    		getPrefix(ANDREW + "\\number") + "\\4.wav",
    		getPrefix(ANDREW + "\\number") + "\\5.wav",
    		getPrefix(ANDREW + "\\number") + "\\6.wav",
    		getPrefix(ANDREW + "\\number") + "\\7.wav",
    		getPrefix(ANDREW + "\\number") + "\\8.wav",
    		getPrefix(ANDREW + "\\number") + "\\9.wav",
    		getPrefix(ANDREW + "\\number") + "\\10.wav",
    		getPrefix(ANDREW + "\\number") + "\\11.wav",
    		getPrefix(ANDREW + "\\number") + "\\12.wav",
    		getPrefix(ANDREW + "\\number") + "\\13.wav",
    		getPrefix(ANDREW + "\\number") + "\\14.wav",
    		getPrefix(ANDREW + "\\number") + "\\15.wav",
    		getPrefix(ANDREW + "\\number") + "\\16.wav",
    		getPrefix(ANDREW + "\\number") + "\\17.wav",
    		getPrefix(ANDREW + "\\number") + "\\18.wav",
    		getPrefix(ANDREW + "\\number") + "\\19.wav",
    		getPrefix(ANDREW + "\\number") + "\\20.wav",
    		getPrefix(ANDREW + "\\number") + "\\30.wav",
    		getPrefix(ANDREW + "\\number") + "\\40.wav",
    		getPrefix(ANDREW + "\\number") + "\\50.wav",
    		getPrefix(ANDREW + "\\number") + "\\60.wav",
    		getPrefix(ANDREW + "\\number") + "\\70.wav",
    		getPrefix(ANDREW + "\\number") + "\\80.wav",
    		getPrefix(ANDREW + "\\number") + "\\90.wav",
    		getPrefix(ANDREW + "\\number") + "\\100.wav"	
    };
    
    static String[] Responses = new String[] {
    		getPrefix(ANDREW) + "\\command\\right2.wav",
    		getPrefix(ANDREW) + "\\command\\mismatch2.wav",
    		getPrefix(ANDREW) + "\\command\\press-again.wav",
    		getPrefix(ANDREW) + "\\command\\wait-beep.wav",
			getPrefix(ANDREW) + "\\command\\press-center.wav",
    };
    
    SoundSet UISounds, UISounds2, CommandSounds, NumberSounds, ResponseSounds;
    
    //the map that stores all the soundsets for different 
    //level of menus. Using this, it
    private static HashMap moSoundSetMap = new HashMap();
    
    private static HashMap moItemNamesMap = new HashMap();
    private static HashMap moStimulusSoundMap = new HashMap();
    
    static final float[] SOUND_POSITIONS = { 0.5F, 0.75F, 1.0F, 0.75F, 0.5F,
			0.25F, 0.0F, 0.25F };

	//=============================================
	//System widgets and listeners
	//=============================================
	ItemListener clistener; // listens to the parent
	public static JPanel moContentPane = null;
	public static JSlider moHSlider = new JSlider(JSlider.HORIZONTAL, -100,
			100, 0);
	public static JLabel moLabel = new JLabel("hello world");
	
	 // A read-only combo box.
    static String[] ITEMS = {"Spiral Mode", "Compact Mode", "Multistroke Marking Mode", "Continuous Marking Mode"};
    static JComboBox readOnlyCB = new JComboBox(ITEMS);
	
	//================================================
	//Variables to store play index, finger positions
	//These variable's value changes often
	//================================================
	static int miX, miY, miLastX, miLastY;
	public static double mdStartAngle = Math.toDegrees(Math.PI / 8.0 + Math.PI / 2.0);
	
	//I am overloading the mfLastZoneIndex value here (this value is used both for audio marking menu and audio
	//pulldown menu to save the state of the item visited, and this is the only solution here
	float mfLastZoneIndex = 0f;
	//int miLastPlayIndex = -1;
	String currentLevelID = "0";
	

	static ArrayList moZoneList = new ArrayList(); //what does this variable do? 
	
	//========================================
	//To be classified
	//=========================================
	//a queue that serve as place holder for computing moving average for finger movements
	//this queue will have a size, the average moving speed is the average value
	//of the queue 
	LinkedList moSpeedQueue = new LinkedList();
    
	//the size of the speed queue, the bigger the size, the slower the movement 
	//of the speed averages, we can adjust the size to adjust the sensitivity for 
	//the detection of the speed changes 
	static final int SPEED_QUEUE_SIZE = 10; 
	
	//private int miStartIndex = 0;

	// The following allows dynamic assignation of offsets to angles and the inner
	// circle radius, to support hysteresis.
	// Value of index 0 is the current amount to add to the radius of the inner circle.
	// Following values are degrees to add to pie borders, starting from the right vertex of
	// the zone 0.
	static final int RADIUS_HYSTERESIS = 3;
	static final int ANGULAR_HYSTERESIS = 3;
	int[] hysteresisOffsets;

	//==========================================
	// Experiment related variables
	//==========================================
	
	static int[] NUMBER_POSITIONS = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 30, 40, 50, 60, 70, 80, 90, 100};
	
	//status of the experiment
	static final short BEFORE_EXPERIMENT = 0;
	static final short BEFORE_TRIAL = 1;
	static final short USER_TAPPED = 2;
	static final short TRIAL_STIMULUS = 3;
	static final short TRIAL_START = 4;
	static final short TRIAL_START_REACTED = 5;
	static final short TRIAL_START_TRIGGERED = 6;
	static final short TRIAL_FINISHED = 7;
	
	private static Logger moDetailedLogger;
	private static Logger moHighLevelLogger;
	
	ExperimentCondition moCurrentCondition;
    ArrayList moCurrentInstructionSet;
    ArrayList moConditionList;
    //String[] maCurrentInstruction;
    //String[] maCurrentResponse;
    ArrayList maCurrentResponse = new ArrayList();
    private Stimulus moCurrentStimulus;
    private final TimerTask pollingTask;

	 //these two variables determines the current menu breadth and depth
	 static int miCurrentMenuBreadth = -1; //temporary set to 12 here
	 static int miCurrentMenuDepth = -1;
	 static int miCurrentExperimentMode = ExperimentCondition.EarPodMenu;
     
    private static String MACHINE_ID;
 	private static final String LOG_CONNECTOR = Logger.msSeparator;
 	
 	static String msUsrID; //this holds the user id
	//private static String msSession;
 	
	static boolean BeforeFirstAudioCondition = true;
	static boolean BeforeFirstVisualCondition = true;

	static short ExperimentStatus; //this variable will hold the current experiment status 	
	
	HashMap moExperimentSet;
 	
 	//==> logging information 
	static int miParticipantID;
 	//private static int miSession;
 	
 	//mdStartDrawing - mdFinishStimulus = reaction time
 	private long mlStartStimulus;
 	private long mlFinishStimulus; 
 	private long mlStartDrawing;
 	private long mlFinishDrawing; 
 	
 	private long mlReactionTime;
 	private long mlExecutionTime;
 	//private String msStimuliCategory;
 	
	 int miConditionIndex = 0;
	 int miTrialIndex = -1; //since we will increase it by one when we start
 	
	int miTotalTrialCount = 0; //in which trial we are at	
	
	//I need to implement the visual menu here, so we can perform the comparison between those 2
	  // A multiple selection list.
    static JList moVisualList;
    static VisualMarkingPanel moVisualMarkingPanel; //panel for visual marking & audio visual marking interface 
    static JWindow moVisualFrame; //the visualFrame that stores the visual interface
    static JFrame moDebugFrame; //
    static JWindow moBackgroundWindow; //the backgroundwindow for the experiment
    static JExperimentNavigator moNavigator; //the window that allows experimenter to select the experimental conditions
    static JExperimentProgress moProgress; //the progressbar in the experiment 
    
    boolean waitForKeyPress = false;
    boolean stopPlayingStimulus = false;
    boolean spacebarPressed = false;
    JWindow[] msgframes = new JWindow[2];
    JLabel iPodLabel;
    
    //in the visual menu, each submenu has an id associate with it. The id indicate the level and index
    //of the submenu. For example, the root is 0, the first child of root is 00, and the second child
    //is 01. This id is key in the moVisualElementMap for the String[]. 
    static String msVisualParentID = "0";
	
    //======================================================
    //TODO: End of variables, start of constructor
    //======================================================
	
	// constructors 
	//try to use this one for now, don't use the 
	//other ones because they can't show the menu correctly
	//the reason is unknown right now, but this one works
	public SimpleStrokeAudioUIbk(ArrayList treeModels, TreeModel markingMenu) {
		this(treeModels, markingMenu, "");
	}

	public void copyAndFilterMenuData(int preorder, int firstItem, int lastItem, int postorder) {
		
		// copy original list
		ArrayList list0 = new ArrayList();
		for (int i=0; i<moFullList.size(); i++) {
			list0.add(moFullList.get(i));
		}	
		
		// 0 - Pre-Randomization of the 1st-level items
		Random rand = null;
		if (preorder > 0) {
			rand = new Random(preorder);
			list0 = ConditionTrialInfo.RandomizeTreeList(list0, rand);
		}
		
		// 1 - Filtering on the 1st-level items
		ArrayList subList = new ArrayList();		
		int min = (firstItem == -1) ? 0 : firstItem;
		int max = (lastItem == -1) ? list0.size()-1 : lastItem;
		for (int i=min; i<=max; i++) {
			subList.add(list0.get(i));
		}

		// 2- Post-Randomization of the 1st-level items
		if (postorder > 0) {
			rand = new Random(postorder);
			subList = ConditionTrialInfo.RandomizeTreeList(subList, rand);
		}
				
		// 3 - Build a clean TreeModel
		moList = new TreeModel();
		for (int i=0; i<subList.size(); i++) {
			TreeModel child = (TreeModel)subList.get(i);
			moList.addChild(child);
		}

		//
		moGraphicModel = new TreeAudibleModel(moList, moMarkingMenu);
	}
	
	/**
	 * Create a Sunburst menu with the specified parameters.
	 *
	 * @param str    is the name of this pie menu.
	 * @param icon   is the icon for this pie menu.
	 * @param radius is the intiial radius of the pie menu.
	 */
	public SimpleStrokeAudioUIbk() {

		//=========================================================
		// setup the touchpad and trying to read its value at 
		// a constant rate
		//==========================================================
		initTouchPad();
		pollingTask = new TimerTask() {

			public void run() {
						
				if (spacebarPressed && moCurrentCondition.ALLOW_CONDITION_SKIP && ExperimentStatus != BEFORE_EXPERIMENT && !waitForKeyPress) {
		   			spacebarPressed = false;
		   			EndCondition();
				}		

				//need to check to see if the x, y position of the mouse has been moved or not	
				// Task here
				int values[] = new int[4];
				readTouchPadValues(values);
//				System.out.print("R");
				if (values[0] == _rawx && values[1] == _rawy && values[2] == _rawz && values[3] == _button) {
					return;
				}
				
				//System.out.println("x is " + values[0] + " y is " + values[1] + " z is " + values[2] + " button is " + values[3]);
				
				_rawx = values[0];
				_rawy = values[1];
				_rawz = values[2];
				_x = values[0];
				_y = values[1];
				_z = values[2];
				int _oldbutton = _button;
				_button = values[3];
				
				if (_oldbutton != _button && ALLOW_TOUCHPAD_SIDEBUTTON) {
					if (_button > 0 && _oldbutton == 0) {
						UISounds2.play(0);
						touchpadButtonPressed(_button);
					} else if (_button == 0 && _oldbutton > 0) {
						UISounds2.play(1);
						touchpadButtonReleased(_button);
					}
				}
				
				// we will skip interpreting the data if it's before the experiment or after a trial
				if(ExperimentStatus == BEFORE_EXPERIMENT || ExperimentStatus == TRIAL_FINISHED || waitForKeyPress) return;
				// same thing if the automatic stimulus did not start
				if (moCurrentCondition.AUTOMATIC_STIMULI && ExperimentStatus == TRIAL_START) return;

				//if the values are outside the hole, return them
				
//				if(_x > TP_HMAX || _x < TP_HMIN) return;
//				if(_y > TP_VMAX || _y <TP_VMIN)return;
				
				//debug("_x is "+ _x + " , _y is " + _y + " _z is "+_z);
				//we no longer uses divided areas
				//current code separate the touchpad into two areas. 
				//area1 is the circular area, while area2 is the horizontal rectangular area 
				
				//if (_x < 1700) {

					//int width = TP_HMAX - TP_HMIN; //this width is assuming
												   // orientation north
					//int height = TP_VMAX - TP_VMIN; //this height is assuming
													// orientation north
					//if (_z < 30)
					//	_z = 0;
					//first perform a translation, to transform the system from
					//(0,0) (width,0)
					//(0,height) (width,height)
					//Into a system
					//      ^ y
					//      |
					//----------------> x
					//      |

					//Well, for the hack version, we don't worry about the
					// angle so much here
					//1. first step, if the value passed in exceeding the
					// maximums and mins, make them
					//the max and mins
					if (_x > TP_HMAX)
						_x = TP_HMAX;
					if (_x < TP_HMIN)
						_x = TP_HMIN;
					if (_y > TP_VMAX)
						_y = TP_VMAX;
					if (_y < TP_VMIN)
						_y = TP_VMIN;

					//2. second step, perform the translation
					_x = _x - TP_HMIN;
					_y = _y - TP_VMIN;

					//2.5 translation so the center of the rect is the origin
					_x = _x - (TP_HMAX - TP_HMIN) / 2;
					_y = _y - (TP_VMAX - TP_VMIN) / 2;

					//3. perform the rotation
					double angle = 0;
					switch (TOUCHPAD_ORIENTATION) {
					case NORTH:
						angle = 0.0;
						break;
					case EAST:
						angle = (Math.PI / 2.0);
						break;
					case SOUTH:
						angle = (Math.PI);
						break;
					case WEST:
						angle = -(Math.PI + Math.PI / 2.0);
						break;
					}

					double tmpX = (Math.cos(angle) * _x - Math.sin(angle) * _y);
					double tmpY = (Math.sin(angle) * _x + Math.cos(angle) * _y);

					//4. translate back
					//notice that when translate back, the x increment and y
					// increment have been swapped
					_x = (int) tmpX + (TP_VMAX - TP_VMIN) / 2;
					_y = (int) tmpY + (TP_HMAX - TP_HMIN) / 2;

					//well, because this operates in a thread, sometimes, certain events
					//will be missed, due to occasionally lose of data.
					//so we need to perform some checking to make sure everything happens logically
					//for example, an empty event should happen after a drag event, or touch event. 
					//since that means we skipped release. 
					if (_z == 0 && _lastZ == 0) {
						_lastEvent = 0;
						//1 and 2 represent touch or dragged
						if(_lastEvent == 1 || _lastEvent == 2){
							//we need to add the finger released event here
							//debug("it happened *****************************************");
							fingerReleasedCircle(_x,_y,_button, _z, _x0, _y0);
							_lastEvent = 3;
						}
						//need to check for last state, if the last event is 
					} else if (_z != 0 && _lastZ == 0) {
						UISounds2.play(0);
						_lastEvent = 1;
						//need to check last state 
						fingerTouchedCircle(_x, _y, _button, _z);
						_x0 = _x;
						_y0 = _y;
					} else if (_z != 0 && _lastZ != 0) {
						_lastEvent = 2;
						//if last state is empty, need to insert 
						fingerDraggedCircle(_x, _y, _button, _z, _x0, _y0);
					} else if (_z == 0 && _lastZ != 0) {
						UISounds2.play(1);
						_lastEvent = 3;
						fingerReleasedCircle(_x, _y, _button, _z, _x0, _y0);
					}

				//}//end of if _x is less than 1700 (which is the area of the
				 // circle
				/*else if (_x > 1800) {
					//we know finger is at the rectangular location
					if (_z == 0 && _lastZ == 0) {

					} else if (_z != 0 && _lastZ == 0) {
						sliderTouched(_x, _y, _button, _z);
					} else if (_z != 0 && _lastZ != 0) {
						sliderDragged(_x, _y, _button, _z);
					} else if (_z == 0 && _lastZ != 0) {
						sliderReleased(_x, _y, _button, _z);
					}
				}*/
				_lastZ = _z;
			}
		};
		
		fingerDetector.scheduleAtFixedRate(pollingTask, miDelay, miPeriod);
	}; // of default constructor
	
	
	/**
	 * Create a Sunburst menu with the specified parameters.
	 *
	 * @param str    is the name of this pie menu.
	 * @param icon   is the icon for this pie menu.
	 * @param radius is the intiial radius of the pie menu.
	 */
	public SimpleStrokeAudioUIbk(ArrayList treeModels, TreeModel markingMenu,
			String str) {
		
		this();

		moMarkingMenu = markingMenu;
		// we allow multiple treemodels to be added to the system and they will be merged into a single list
		moFullList = treeModels;

		// simply copy the original list into the working list
		copyAndFilterMenuData(0, -1, -1, 0);
	
	}; // of default constructor


	/**
	 * A mouse listener on one of the top-level panes, allowing us to
	 * listen on mouse events and popup the menu when appropriate.
	 */
	static final class ItemListener implements KeyListener {

		//I am going to use keypressed as my counter for different 
		//voice files. 
		int keyPressedCount = 0;
		
		private SimpleStrokeAudioUIbk menu = null;
		
		public void setAudioUI(SimpleStrokeAudioUIbk ui){
			menu = ui;
		}

		public void keyPressed(KeyEvent evt) {
			//System.out.println("Key pressed " + keyPressedCount);
			//keyPressedCount++;
			
			 // Check for key characters.
            if (evt.getKeyChar() == 't') {
            	menu.increaseTrialIndex(1);
           		menu.EndTrial(false);
            }
            else if(evt.getKeyChar() == 'c') {
           		menu.EndCondition();
            }
            //allow the user to hit the keys like '1', '2', '3', '4' to switch among
            //experimental conditions 
            else if(evt.getKeyChar() == '1'){
            	moNavigator.changeExperimentCondition(0);
            }
            else if(evt.getKeyChar() == '2'){
            	moNavigator.changeExperimentCondition(1);
            }
            else if(evt.getKeyChar() == '3'){
            	moNavigator.changeExperimentCondition(2);
            }
            else if(evt.getKeyChar() == '4'){
            	moNavigator.changeExperimentCondition(3);
            }
            
		}

		public void keyTyped(KeyEvent evt) {
			//System.out.println("Key pressed " + keyPressedCount);
		}

		public void keyReleased(KeyEvent evt) {

		}

	} // of ItemListener
	
	//======================================================
    //TODO: End of constructors, start of interaction technique
    //======================================================
	
	//let me put a place holder for mlStartDrawing
	
	public void touchpadButtonPressed(int button) {
		if (moCurrentCondition.ALLOW_CONDITION_SKIP && ExperimentStatus != BEFORE_EXPERIMENT && !waitForKeyPress) {
			// End condition
	    	debug("end condition");
    		EndCondition();
		} else if ((!moCurrentCondition.ALLOW_CONDITION_SKIP) && ExperimentStatus == BEFORE_TRIAL) {
			// Show progress bar
			moProgress.setVisible(moCurrentCondition.SHOW_PROGRESS);
		}
	}
	
	public void touchpadButtonReleased(int button) {
		if ((!moCurrentCondition.ALLOW_CONDITION_SKIP) && ExperimentStatus == BEFORE_TRIAL) {
			// Show progress bar
			moProgress.setVisible(false);
		}
	}
	
	public void spacebarPressed() {
		spacebarPressed = true;
	}
	
	// main interaction techniques
	/**
	 * finger starts to touch the touchpad
	 */
	public void fingerTouchedCircle(int x, int y, int button, int z) {
		
		miX = x;
		miY = y;		
		
		logDetailedEvent(TOUCH_EVENT, "", x, y, z, button);
		
		//need to set the relative x and y when finger touch the touchpad
		//by the way, what's relative x and y now (I believe it's only related to visual conditions)
		//but not audio conditions. (which doesn't really make sense to me) 
		Point p = new Point(x, y);
		Point p1 = this.touchPadCornerToScreen(p);
		Point p2 = this.screenCornerToCenter(p1);
		miLastRelativeX = (int) p2.getX();
		miLastRelativeY = (int) p2.getY();
		
		//1. set the paint status
		PaintStatus = CLEAR_SCREEN;

		//2. init gesture and add the first point  
		moGesture = null;
		moGesture = new Gesture(100);
		moGesture.addPoint(x, y, z);
		
		mfLastZoneIndex = -1;
		//2.5 
		//experiment status check, there are several status
		//The experiment procedure is 
		//Before_Trial (require user to tap the touchpad to start
		//Trial_Stimulus (read the stimulus to the user, accept no input)
		//Trial_Start (start to accept user input, after reading the stimulus) 
		//Trial_finished (user finished input, and response is given to the user) 

		if (moCurrentCondition.AUTOMATIC_STIMULI) {
			if (ExperimentStatus == BEFORE_TRIAL || ExperimentStatus == TRIAL_FINISHED) {
				UISounds2.play(6); // wait for the simulus, please.
				return;
			}
			if (ExperimentStatus == TRIAL_STIMULUS || ExperimentStatus == TRIAL_START) {
				handleUserReactedEvent();
				setExperimentStatus(TRIAL_START_TRIGGERED);
			}
		} else {
			if(ExperimentStatus == BEFORE_TRIAL){
				int zone = getWeightedZoneN(x, y, 0, 1, null, 0);
				if (zone == -3 || zone == -2) {
					handleStartStimulusEvent();
				} else {
					UISounds2.play(6);
				}
				return;
			}
			if(ExperimentStatus == TRIAL_STIMULUS){
				handleUserReactedTooSoonEvent();
				return;
			}
			if(ExperimentStatus == TRIAL_START){
				handleUserReactedEvent();
			}
			if (ExperimentStatus == TRIAL_START_REACTED) {
				setExperimentStatus(TRIAL_START_TRIGGERED);
			}
		}

		if (ExperimentStatus != TRIAL_START_TRIGGERED) return;

		

		if (conditionToMode(moCurrentCondition)== PULLDOWN_MODE 
		){
			ContinuousControl.clearPoints();
			ContinuousControl.addPoint(new Point(x, y));
			moVisualList.setSelectedIndex(0);
			if (miCurrentExperimentMode == ExperimentCondition.AudioPulldownMenu ||
				miCurrentExperimentMode == ExperimentCondition.AVPulldownMenu) {
				SoundSet sSet = (SoundSet) moSoundSetMap.get(msVisualParentID);
				sSet.play(0);
			}
		} else if(conditionToMode(moCurrentCondition)== EARPOD_MODE 
				){
			clearHysteresis();
			
			//4. compute and handle the zone information here
			//only if we are at the browsing modes
			
			mfLastZoneIndex = computeAndHandleZoneForCurrentItem(x, y, false, SPIRAL_ITEM_LIMIT); //item selected is false here
			
		///dealing with spiral mode here
		}else if(conditionToMode(moCurrentCondition)== SPIRAL_MODE){

			//3.5 handle the spiral zone case 
			handleSpiralZone(x, y);
			
			//4. compute and handle the zone information here
			//only if we are at the browsing modes
			mfLastZoneIndex = computeAndHandleZoneForCurrentItem(x, y, false, SPIRAL_ITEM_LIMIT); //item selected is false here
		}//end of spiral mode
		else if(conditionToMode(moCurrentCondition)== COMPACT_MODE){
			mfLastZoneIndex = computeAndHandleZoneForCurrentItem(x, y, false, SPIRAL_ITEM_LIMIT);
		}//end of compact mode 
		
		//6. paint and set the paint status again 
		repaint();
		PaintStatus = PAINT_STROKE_COMPONENT;
		//5. record a set of values
		miLastX = miX;
		miLastY = miY;
	}
	
	//private static float mfAngle = 0.0F;
	//private static int miLastStableX = -4, miLastStableY = -4; //any number less than -3 will do since the stable offset is 3
	//private static final int STABLE_OFFSET = 10;
	
	private static int miLastSelectedVisualIndex = 0;
	private static int miLastRelativeX, miLastRelativeY;
	private static double HalfZoneSpan = 40.0;//15.0;

	/**
	 * finger drags on the touch pad
	 * @param x
	 * @param y
	 * @param button
	 */
	public void fingerDraggedCircle(int x, int y, int button, int z, int x0, int y0) {
		repaint();
		miX = x;
		miY = y;
		
		//0. log this event into the detailed log
		logDetailedEvent(DRAG_EVENT, "", x, y, z, button);
		
		//debug("x is "+ x + ", y is "+ y + " z is "+ z);
		//1. record the points for gesture object
		if (moGesture == null)
			moGesture = new Gesture(100);
		moGesture.addPoint(x, y, z);
		
		// "explicit" drag.
		if ((!moCurrentCondition.AUTOMATIC_STIMULI) && Math.sqrt((x-x0)*(x-x0) + (y-y0)*(y-y0)) > DRAG_REACTION_SENSITIVITY) {
			if (ExperimentStatus == TRIAL_STIMULUS) {
				handleUserReactedTooSoonEvent();
				return;
			}
			if (ExperimentStatus == TRIAL_START) {
				handleUserReactedEvent();
				return;
			}
		}
		
		//1.5 
		//experiment status check, there are several status
		//The experiment procedure is 
		//Before_Trial (require user to tap the touchpad to start
		//Trial_Stimulus (read the stimulus to the user, accept no input)
		//Trial_Start (start to accept user input, after reading the stimulus) 
		//Trial_finished (user finished input, and response is given to the user) 
		if(ExperimentStatus < TRIAL_START_TRIGGERED){
			//skip to the end 
			return;
		}
		
		//2. Instead of handlingthe continuous case, I am using it to handle 
		//   the touchpad case. 
/*		if(miCurrentExperimentMode == ExperimentCondition.AudioPulldownMenu){
			Point p = new Point(x, y);
			Point p1 = this.touchPadCornerToScreen(p);
			Point p2 = this.screenCornerToCenter(p1);
			x = (int) p2.getX();
			y = (int) p2.getY();
			
			ContinuousControl.addPoint(new Point(x, y));
			
			int selectedIndex = 0; //TODO the current index of some list;
			int indexChange = getRelativeZoneChange(miLastRelativeX, miLastRelativeY, x, y, HalfZoneSpan);
			int newValue = 0;
			int maxNumOfItems = 0; //TODO get the current list size
			
			//boolean setLastItem = false; //the valuable that determines whether or not our selection is on the last item or not
			boolean playAudio = true;
			
			//using the index change of visual menu to set the index for the audio item 
			if(indexChange != 0){
				if(indexChange > 0){
					newValue= selectedIndex + Math.abs(indexChange);
					if (newValue > maxNumOfItems-1) {
						newValue = maxNumOfItems-1;
						playAudio = false;
					}
				}
				else {
					newValue= selectedIndex - Math.abs(indexChange);
					if (newValue < 0) {
						newValue = 0;
						playAudio = false;
					}
				}
				
				//4. play the audio for the new value  
				playItem(moLastItem, newValue, false); //in finger drag, we won't select the time 
				
				//5. post selection cleanup work
				miLastRelativeX = x;
				miLastRelativeY = y;
				
				ContinuousControl.clearPoints();
				ContinuousControl.addPoint(new Point(x,y));
				 
				mfLastZoneIndex = (float) newValue;
				
				if(playAudio)
					UISounds.play(0);
			}//end of index != 0
			//perform the necessary work here
		}*/
		
		if (conditionToMode(moCurrentCondition)== PULLDOWN_MODE) {
			
			if(moVisualList == null) return;
			
			Point p = new Point(x, y);
			Point p1 = this.touchPadCornerToScreen(p);
			Point p2 = this.screenCornerToCenter(p1);
			x = (int) p2.getX();
			y = (int) p2.getY();
			
			ContinuousControl.addPoint(new Point(x, y));
			
			int selectedIndex = moVisualList.getSelectedIndex();
			int indexChange = getRelativeZoneChange(miLastRelativeX, miLastRelativeY, x, y, HalfZoneSpan);
			int newValue = 0;
			int maxNumOfItems = moVisualList.getModel().getSize();
			boolean playAudio = true;
			
			if(indexChange != 0){
				//boolean direction = ContinuousControl.getDirection();
				//if (direction == ContinuousControl.CLOCKWISE) {
				if(indexChange > 0){
					newValue= selectedIndex + Math.abs(indexChange);
					if (newValue > maxNumOfItems-1) {
						newValue = maxNumOfItems-1;
						playAudio = false;
					}
				}
				else {
					newValue= selectedIndex - Math.abs(indexChange);
					if (newValue < 0) {
						newValue = 0;
						playAudio = false;
					}
				}
				
				moVisualList.setSelectedIndex(newValue);
				moVisualList.ensureIndexIsVisible(newValue);
				//miLastSelectedVisualIndex = newValue;
				//miCurrentSelectedVisualIndex = newValue;
				
				miLastRelativeX = x;
				miLastRelativeY = y;
				
				ContinuousControl.clearPoints();
				ContinuousControl.addPoint(new Point(x,y));
				
				repaint();
				
				if(playAudio) {
					if (miCurrentExperimentMode == ExperimentCondition.AudioPulldownMenu ||
							miCurrentExperimentMode == ExperimentCondition.AVPulldownMenu) {
						SoundSet sSet = (SoundSet) moSoundSetMap.get(msVisualParentID);
						sSet.play(newValue);
					}
					UISounds.play(2);
				}
			}//end of if(index!=0)
		}//end of current mode is continuous mode
		else if(conditionToMode(moCurrentCondition)== EARPOD_MODE
				){

			int previousZone = Math.round(mfLastZoneIndex);
			
			//4. compute and handle the zone information here
			//only if we are at the browsing modes
			if(CurrentMode == SPIRAL_MODE || CurrentMode == COMPACT_MODE)
				mfLastZoneIndex = computeAndHandleZoneForCurrentItem(x, y, false, SPIRAL_ITEM_LIMIT); //item selected is false here

			if(miCurrentExperimentMode != ExperimentCondition.EarPodMenu){
				moVisualMarkingPanel.setSelectedIndex((int)mfLastZoneIndex);
				moVisualMarkingPanel.repaint();
			}
			
			if (previousZone != mfLastZoneIndex) {
				setHysteresis(previousZone, Math.round(mfLastZoneIndex));
				/// also want to play the click sound to separate item from items
				UISounds.play(2);
			}
		}
		///here, let's handle the other case here 
		else if(conditionToMode(moCurrentCondition)== SPEEDZOOM_MODE){
			
			double distance = ContinuousControl.getLength(new Point(miX, miY), new Point(miLastX, miLastY));
			
			addToSpeedQueue((int) distance);
			
			int speedAverage = getSpeedAverage();
			//onScreenMessage("distance is "+ distance);
			//System.out.println("distance is "+ distance);
			if(LastScrollingState == -1) LastScrollingState= FAST_STATE;
			else{
				LastScrollingState = CurrentScrollingState;
			}
			
			if(speedAverage < 30.0) CurrentScrollingState = SLOW_STATE;
			else{
				CurrentScrollingState = FAST_STATE;
			}

			//4. compute and handle the zone information here
			//only if we are at the browsing modes
			
			mfLastZoneIndex = computeAndHandleZoneForCurrentItem(x, y, false, SPIRAL_ITEM_LIMIT); //item selected is false here
			
		}
		else if(conditionToMode(moCurrentCondition)== SPIRAL_MODE){			
			handleSpiralZone(x, y);
			mfLastZoneIndex = computeAndHandleZoneForCurrentItem(x, y, false, SPIRAL_ITEM_LIMIT); //item selected is false here		
		}//end of spiral mode 
	
//		3. record a few values 
		miLastX = miX;
		miLastY = miY;
		
	}//end of finger dragged
	
	//private static final int TAB_DISTANCE_LIMIT = 100;
	private static final int TAB_SIZE_LIMIT = 20;
	
	/**
	 * finger release from the touchpad
	 * @param x
	 * @param y
	 * @param button
	 */
	public void fingerReleasedCircle(int x, int y, int button, int z, int x0, int y0) {

		//0. log this event into the detailed log
		logDetailedEvent(RELEASE_EVENT, "", x, y, z, button);
		
		//1. finish the gesture point
		moGesture.addPoint(x, y, z); 
	
		//2. actually, because the shape of the finger, when release, the contact point 
		//often changes which causes unnecessary error. To make the selection more robust
		//we remove the last three contact points from the gesture, and take the 4th last point
		int jitter_size = 4;
		
		if(moGesture.size() > jitter_size){
			TimedPolygon points = moGesture.getPoints();
		    int[] xPoints = points.xpoints;
		    int[] yPoints = points.ypoints;
		    int length = xPoints.length;
		    x = xPoints[length - jitter_size];
		    y = yPoints[length - jitter_size];
		}
		
		//2. set the coordinates 
		miX = x;
		miY = y;
		
		//double distance = ContinuousControl.getLength(new Point(moGesture.lastx, moGesture.lasty), new Point(tPoly.xpoints[0], tPoly.ypoints[0]));
		//debug("distance is "+ distance + " size is " + moGesture.size());
		
		//boolean userTapped = false;
		if(moGesture.size() < TAB_SIZE_LIMIT){
			fingerTappedCircle(x, y, button, z);
		}//end of tap handling
		

		if (moCurrentCondition.AUTOMATIC_STIMULI) {
			if (ExperimentStatus != TRIAL_START_TRIGGERED) {
				return;
			}
			
		} else {
			if (ExperimentStatus == TRIAL_STIMULUS) {
				handleUserReactedTooSoonEvent();
				return;
			}
			if (ExperimentStatus == TRIAL_START) {
				handleUserReactedEvent();
				return;
			}
		}
		
		//2.5 
		//experiment status check, there are several status
		//The experiment procedure is 
		//Before_Trial (require user to tap the touchpad to start
		//Trial_Stimulus (read the stimulus to the user, accept no input)
		//Trial_Start (start to accept user input, after reading the stimulus) 
		//Trial_finished (user finished input, and response is given to the user) 
		if (ExperimentStatus == TRIAL_START_TRIGGERED) {
			// debug("change status");
			// skip to the end
			// setExperimentStatus(TRIAL_START; //this enforce there won't be any
			// actions before the stimulus is finished.
			// the touchpad information won't pass down during the stimulus
			// period.
			// mlFinishStimulus = Logger.getTime();
			// return;

			/*if (miCurrentExperimentMode == ExperimentCondition.AudioPulldownMenu) {
				//select the last selected item there 
				handleBrowsingCommands(Math.round(mfLastZoneIndex), true);
			}// end of if
			
			else*/if (conditionToMode(moCurrentCondition)== PULLDOWN_MODE) {
				// doing nothing
				if (ExperimentStatus > TRIAL_STIMULUS && ExperimentStatus < TRIAL_FINISHED) {
					// we know the status is greater than USER_TAPPED
					// we will go further
					UISounds.play(0);
					SelectVisualPulldownItem();
					return;
					// for audio menu, this means nothing
				}// end of status between stimulus and finished trial
			} else if (conditionToMode(moCurrentCondition)== EARPOD_MODE
					||conditionToMode(moCurrentCondition)== SPIRAL_MODE
					||conditionToMode(moCurrentCondition)== COMPACT_MODE) {
				// 3. compute and handle the zone information here
				// only if we are at the browsing modes
				
				mfLastZoneIndex = computeAndHandleZoneForCurrentItem(x, y, true, SPIRAL_ITEM_LIMIT); // item selected is
														// false here

					// I need to clear the lastZoneIndex, because audio play
					// check to see if the
					// the current zone is equal to last zone or not, and only
					// plays the audio
					// if zones are not the same. But at the end of the each
					// trial, we need to
					// reset the zone, so we will play sound at each of the new
					// trials.
				mfLastZoneIndex = -1F;
					// 3.5. clear the zonelist
				moZoneList.clear();
				clearHysteresis();
				
				if(miCurrentExperimentMode == ExperimentCondition.VisualMarkingMenu ||
					miCurrentExperimentMode == ExperimentCondition.AVEarPodMenu){
					if (ExperimentStatus > TRIAL_STIMULUS && ExperimentStatus < TRIAL_FINISHED) {
						// we know the status is greater than USER_TAPPED
						// we will go further
						UISounds.play(0);
						SelectVisualMarkingItem();
						return;
					}// end of status between stimulus and finished tria	
				}//end of visualmarking or audiovisualmarking
				
				
			}// end of else if audio mode
			
		}// end of experimentstatus > trial_stimulus
		// 5. paint the changes and set the status
		PaintStatus = PAINT_STROKE;
		repaint();

		miLastX = miX;
		miLastY = miY;
	}
	
	
	private void handleStartStimulusEvent() {
		if(miTrialIndex == moCurrentInstructionSet.size()){
			if(!DEMO_MODE){
				EndCondition();
				return;
			}
			else{
				miTrialIndex = 0;
			}
		}
			
		moCurrentStimulus = (Stimulus) moCurrentInstructionSet.get(miTrialIndex);
		//play the stimulus according to the menu and the instruction
		setExperimentStatus(TRIAL_STIMULUS);
			
		//debug("Trial number: " + miTrialIndex);
					
		PlayStimulus(moCurrentStimulus);
			
		return;
	}
	
	private void handleUserReactedTooSoonEvent() {
		if (ExperimentStatus == TRIAL_STIMULUS) {
			stopPlayingStimulus = true;
			EndTrial(true);
		}
	}
	
	private void handleUserReactedEvent() {
		//record the starting time
		if(mlStartDrawing <= 0)
			mlStartDrawing = Logger.getTime();
		setExperimentStatus(TRIAL_START_REACTED);
		if(moCurrentCondition.AUDIO_STIMULI) {
			setStimuliVolume(0.75f); // this is a kind of compressor
		}
	}
	
	
	/**
	 * Convert the stimulus (which consist of numbers) to text 
	 * that can displayed in the visual menu so users can select 
	 * them 
	 * 
	 * @param stimuliStrs
	 * @return
	 */
	String StimuliToVisualText(String[] stimuliStrs) {
		if(stimuliStrs == null || stimuliStrs.length == 0) return null;
		
		String id = "0";
		
		StringBuffer title = new StringBuffer();
		
		for(int i=0; i< stimuliStrs.length-1; i++){

			String[] items = (String[]) moItemNamesMap.get(id);

			String str = stimuliStrs[i];
			int index = 0;
			
			try{
				index = Integer.parseInt(str);
			}catch(Exception e){
				index = 0;
			}
			
			title.append(items[index] + ", ");
			
			id = id + index;
		}
		
		String[] items = (String[]) moItemNamesMap.get(id);
		String str = stimuliStrs[stimuliStrs.length-1];
		int index = 0;
		try{
			index = Integer.parseInt(str);
		}catch(Exception e){
			index = 0;
		}
		
		try {
			title.append(items[index]);
		} catch (Exception e) {
			e.printStackTrace();
		}
		
		return title.toString();
	}
	
	/**
	 * This event is a compound event taken from a compound event 
	 * @param x
	 * @param y
	 * @param button
	 * @param z
	 */
	public void fingerTappedCircle(int x, int y, int button, int z){		
		debug("finger tapped " + ExperimentStatus);
		//handles play the stimulus for both 
	}
	
	/**
	 * check to see if the current zone is different from the last zone
	 * return the zone change delta according
	 * @param x
	 * @param y
	 * @param halfZoneSpan
	 * @return
	 */
	private int getRelativeZoneChange(int lastX, int lastY, int x, int y, double halfZoneSpan){
		
		double angle1 = getAngleInDegrees(lastX, lastY); // TMP HACK
		double angle2 = getAngleInDegrees(x, y); // TMP HACK
		//debug("angle1 is "+ angle1 + " angle 2 is "+ angle2);

		double dif = angle2 - angle1;
		
		if(dif > 180.0){
			dif -= 360.0;
		}
		else if (dif < -180.0){
			dif += 360.0;
		}

		if(Math.abs(dif) > halfZoneSpan){
			if(dif > 0) return 1;
			else if(dif< 0) return -1;
		}else{
			return 0;
		}
		return 0;
	}
	
	void clearHysteresis() {
		// get the number of zones 
		int numItems = miCurrentMenuBreadth;
		hysteresisOffsets = new int[numItems+1];
	}
	
	void setHysteresis(int lastzone, int newzone) {
		// get the number of zones 
		int numItems = miCurrentMenuBreadth;
		hysteresisOffsets = new int[numItems+1];
		
		// rename zone 0 for convenience
		if (lastzone == 0) lastzone = numItems;
		if (newzone == 0) newzone = numItems;
		
		if (lastzone >0 && newzone == -3) {
			// Case #1: pie -> inner circle = expand circle
			hysteresisOffsets[0] = RADIUS_HYSTERESIS;
		} else if (lastzone == -3 && newzone > 0) {
			// Case #2: inner circle -> pie = shrink circle
			hysteresisOffsets[0] = -RADIUS_HYSTERESIS;
		} else if ((lastzone > 0 && newzone == lastzone+1) || (lastzone == numItems && newzone == 1)) {
			// Case #3: pie -> next pie = move the frontier angle away
			hysteresisOffsets[newzone] = ANGULAR_HYSTERESIS;
		} else if ((newzone > 0 && newzone == lastzone-1) || (lastzone == 1 && newzone == numItems)) {
			// Case #3: pie -> previous pie = move the frontier angle away
			if (lastzone == 0) newzone = 8;
			hysteresisOffsets[lastzone] = -ANGULAR_HYSTERESIS;
		} else {
			// reset
		}
	}
	
	
	//======================================================
    //TODO: End of interaction, start of handling methods
    //======================================================
	
	//private HashMap moWeightsCache = new HashMap();
	//private static int WINDOW_SIZE = 5;
	//actually, the window weight variable might change
	//private static int WINDOW_WEIGHT = 20; 
	
	/**
	 * Compute and the handle the zones for the current selected item
	 * A method used to handle situation in browsing command mode 
	 * For expert command mode, we use either multi-stroke marking menu 
	 * or some other technique. 
	 * 
	 * There is a problem for computeAndHandleZone here for the dynamic shifting
	 * zone compact mode. When we going from the end of the circle to the beginning 
	 * again, or from the beginning to the end, the computation of the zones will 
	 * be going back and forth. This is due to the incompatibility between the circular
	 * continuous nature of the touchpad, and the linear nature of the algorithm used to
	 * determine the zones. Earlier, if the finger is at a zone close to the border between
	 * the beginning and the end, I will assign the weights to the other end of the circle 
	 * for example, if the finger is at zone 0, the window covers -2, -1, 0, 1, 2, I will 
	 * assign the new weight to length - index, so if the index is -2, length is 41, the new
	 * weight will be assigned to slot 39, however, this cause a shift problem in calculation of the 
	 * zones in getZoneWeightedN since it's taking the cumulative of the zoneWeight/totalweight to adjust
	 * the angles. change the sequence like that causes a nonlinear jump to a linear in nature shift. 
	 * 
	 * To solve this problem, i will increase the weight size by the size of the window, and set the first 
	 * @param x
	 * @param y
	 * @param itemSelected
	 * @param itemLimit
	 * @return
	 */
	public float computeAndHandleZoneForCurrentItem(int x, int y, boolean itemSelected, int itemLimit){
		
		//4. get the zone information 
		int numOfItemsThisLevel = 0;
		if(moLastItem == null){
			//we are at the root
			numOfItemsThisLevel = moGraphicModel.getLayer(0).size();
		}
		else{
			//check the leaf level 
			numOfItemsThisLevel = moLastItem.getChildCount();
		}
		
		//5. get the zone index for 
		float zoneIndex = 0F;
		//if it's spiral mode and number of items is greater than 8, then
		//we use spiral mode
		if(conditionToMode(moCurrentCondition) == SPIRAL_MODE && numOfItemsThisLevel > itemLimit){
			zoneIndex = getZoneSpiral(x, y, mdStartAngle, itemLimit, moZoneList);
			handleBrowsingCommands(Math.round(zoneIndex), itemSelected);
			// if end of trial, return
			if (ExperimentStatus == BEFORE_TRIAL || ExperimentStatus == TRIAL_FINISHED)
				return 0;
		}
		
		//if it is spiral mode, but number of item is less than 8,
		//we use the compact mode
		if (conditionToMode(moCurrentCondition)== EARPOD_MODE || 
				conditionToMode(moCurrentCondition)== SPEEDZOOM_MODE
				|| (conditionToMode(moCurrentCondition)== SPIRAL_MODE && numOfItemsThisLevel <= itemLimit)) {
			//6. handle the command mode here
			if (conditionToMode(moCurrentCondition)== SPIRAL_MODE && numOfItemsThisLevel <= itemLimit ) {
				zoneIndex = getWeightedZoneN(x, y, mdStartAngle,
						numOfItemsThisLevel, null, 0);
//System.out.println("-----------" + statusToString(ExperimentStatus));
				handleBrowsingCommands(Math.round(zoneIndex), itemSelected);
				//debug("handlebrowsingcommands " + zoneIndex);
				return zoneIndex;
			}

			//so the routine is like this
			//System.out.println(CurrentScrollingState + ", "+ LastScrollingState);
			
			Point p = new Point(x, y);
			Point p1 = this.touchPadCornerToScreen(p);
			Point p2 = this.screenCornerToCenter(p1);
			x = (int) p2.getX();
			y = (int) p2.getY();
			
			p = new Point(miLastX, miLastY);
			p1 = this.touchPadCornerToScreen(p);
			p2 = this.screenCornerToCenter(p1);
			int lastX = (int) p2.getX();
			int lastY = (int) p2.getY();
			
			float indexChange = getDeltaIndexChange(x, y, lastX, lastY, CurrentScrollingState, numOfItemsThisLevel);
			
			zoneIndex = mfLastZoneIndex + indexChange;
			
			//the following statements using continuous span to circling around the space
			//if(zoneIndex < 0F) zoneIndex += numOfItemsThisLevel;
			//zoneIndex = zoneIndex%numOfItemsThisLevel;
			
			//instead of continuous, I will try limited span here
			if(zoneIndex < 0F) zoneIndex = 0;
			else if(zoneIndex > (numOfItemsThisLevel-1)) zoneIndex = numOfItemsThisLevel-1;
			
			handleBrowsingCommands(Math.round(zoneIndex), itemSelected);

			/*if (CurrentMode == CONTINUOUS_STROKE_MODE) {
				zoneIndex = getWeightedZoneN(x, y, mdStartAngle,
						numOfItemsThisLevel, null, 0);
				handleContinuousStrokeSelection(Math.round(zoneIndex), Math.round(mfLastZoneIndex));
			}*/
		}
		
		//7. show the debug information of the zone on the screen 
		onScreenMessage("Zone " + zoneIndex);
		
		return zoneIndex;
	}
	
	public float getDeltaIndexChange(int x, int y, int lastX, int lastY, int mode, int nOfItems){
		//first, get the current angle
		if((x == 0 && y == 0) || (lastX == 0 && lastY == 0)) return 0F;
		
		float angleChange = SimpleStrokeAudioUIbk.getAngleChange(x, y, lastX, lastY);
		
		float k = 1F;
		float deltaIndexChange = 0F;
		if(mode == FAST_STATE){
			k = 360F/nOfItems;
		}
		else if(mode == SLOW_STATE){
			k = 20*360F/nOfItems;
		}
	
		deltaIndexChange = - angleChange/k;
		
		return deltaIndexChange;
	}
		
	/**
	 * For the audio condition only
	 * @param zoneIndex
	 * @param setLastItem
	 */
	private void handleBrowsingCommands(int zoneIndex, boolean setLastItem) {
	
			switch (ExperimentStatus) {
			case TRIAL_START_TRIGGERED:
				//debug("zoneIndex is "+ zoneIndex + ", lastZoneIndex is "+ mfLastZoneIndex);
				//if (zoneIndex > -1 && zoneIndex != Math.round(mfLastZoneIndex)
				if (zoneIndex == -3 && Math.round(mfLastZoneIndex) != -3 && Math.round(mfLastZoneIndex) != -2) {
					// play the "center" sound as any other command
					UISounds2.play(2);
					if(miCurrentExperimentMode!= ExperimentCondition.VisualMarkingMenu)
						playItem(null, zoneIndex, setLastItem);
				} else if (zoneIndex > -1 && zoneIndex != Math.round(mfLastZoneIndex)
						|| (setLastItem == true && zoneIndex > -1)) {

					//we don't want to repeat playing the item to be selected now, only a sound to indicate it has been
					//selected
					if(setLastItem != true){
						if(miCurrentExperimentMode!= ExperimentCondition.VisualMarkingMenu)
							playItem(moLastItem, zoneIndex, setLastItem);
						debug("playing item " + zoneIndex);
					}
					else{
						//we know the it's a selection, play the selection sound
						UISounds.play(0);
						//and at the same time, record the finishing time
						mlFinishDrawing = Logger.getTime();
						
						//time to calculate the reaction time and execution time
						if (moCurrentCondition.AUTOMATIC_STIMULI)
							mlReactionTime = mlStartDrawing - mlStartStimulus;
						else
							mlReactionTime = mlStartDrawing - mlFinishStimulus;
						mlExecutionTime = mlFinishDrawing - mlStartDrawing;
				    	debug("REACTION=" + mlReactionTime + " EXECUTION=" + mlExecutionTime);
					}
					
					AudibleMenuItem item = selectItem(zoneIndex, setLastItem);
					
					if(item == null) return;
					
					if(setLastItem == true) {
						maCurrentResponse.add(zoneIndex + "");
						currentLevelID = item.getID();
					}
					
					//finally check to see if it's a leaf node or not
					if(item.getChildCount() == 0){
						//we know it's a leaf node 
						if (setLastItem == true) {
							//playEndSelection();
							moLastItem = null;
							//setExperimentStatus(BEFORE_TRIAL;
							//instead of doing this, I should perform something
							//like the end of Trial
							EndTrial(false);
						}//end of if(setLastItem is true)
						return;
					}//end of checking the leaf node 
				}//end of handling if it's a valid zone or not
				break;
			}//end of switch 
	}//end of at the initStatus
	
	//a boolean variable only used in continuous stroke selection mode 
	//to determine if the finger has left the center and entered the 
	//browsing node state or not
	private boolean mbBrowsingInContinuousStrokeSelection = false;
	
	private int miLastValidZoneIndex = -1;
	
	
	/**
	 * get the item in focus under the zone. We can either browse 
	 * the item (not selecting it or execute the item. setLastItem
	 * will determine that. If setLastItem is true, that means we 
	 * want to select it, otherwise, we just want to browse it. 
	 * @param zoneIndex
	 * @param setLastItem
	 * @return
	 */
	private AudibleMenuItem selectItem(int zoneIndex, boolean setLastItem) {
		AudibleMenuItem item = null;
		if (moLastItem == null) {
			if (zoneIndex > -1 && zoneIndex < moGraphicModel.getLayer(0).size()) {//moGraphicModel.getLayer(0).size())
				item = (AudibleMenuItem) moGraphicModel.getLayer(0).get(
						zoneIndex);

				if (setLastItem == true) {
					moLastItem = item;
				}
			}//end if zoneIndex is valid
		} else {
			if (zoneIndex > -1 && zoneIndex < moLastItem.getChildCount()) {
				item = moLastItem.getChildAt(zoneIndex);

				if (setLastItem == true) {
					moLastItem = item;
				}
			}//end of if(zoneIndex < moLastItem.getChildCount()
		}
		return item;
	}//end of method
	
	/**
	 * play an item
	 * @param item the menu item in focus
	 * @param setLastItem if it's true, that means we are selecting it, otherwise, we are only previewing it
	 */
	private void playItem(AudibleMenuItem item, int zoneIndex, boolean setLastItem ){
		SoundSet sSet = null;
		if(item != null) {
			sSet = (SoundSet) moSoundSetMap.get(item.getID());
		} else {
			sSet = (SoundSet) moSoundSetMap.get(currentLevelID);
		}
				
		if (setLastItem == true) {
			if (PlayCommands == true) {
				CommandSounds.play(YOU_SELECTED);
				try {
					sleep(900);
				} catch (Exception e) {
				}
			}//end of if(playcommands) 
		}
		
		// play clickWheel
		if (zoneIndex < 0) {
			UISounds2.play(2); // play click in center
			zoneIndex = sSet.samples.length-1; // the index to play will be the last item (silence)
		} else if (CurrentMode != COMPACT_MODE) {
			UISounds.play(zoneIndex%8 + 2);
		} else {
			int numOfItemsThisLevel =0;
			if(item!= null) numOfItemsThisLevel= item.getChildCount();
			else {
				numOfItemsThisLevel = moGraphicModel.getLayer(0).size();
			}
			int uiIndex = (int) ((zoneIndex/(float)numOfItemsThisLevel)*20) + 2;
			if(uiIndex  > numOfItemsThisLevel) 
				debug("");
			
			UISounds.play(uiIndex);
			//debug("play ui sound " + uiIndex + " numOfItemsThislevel is "+ numOfItemsThisLevel);
		}
		
		if (sSet != null)
			sSet.play(zoneIndex);
	}
	
	/**
	 * play the end of the selection of a leaf node 
	 *
	 */
	private void playEndSelection(){
		
		if (PlayCommands == true) {
			try {
				sleep(1500);
			} catch (Exception e) {
			}
			CommandSounds.play(LEAF_NODE);
			try {
				sleep(1800);
			} catch (Exception e) {
			}
			CommandSounds.play(INIT_COMMAND);
		}//end of playcommands 
	}

	/**
	 * get the current zone the finger is at
	 * basically, I try to divide the outer touchpad into 8 zones if the radius is greater than
	 * certain threshold, it will be in one of these 8 outer zones, if the radius is less than 
	 * certain certain number it's in the inner circle. Otherwise, it will be in the middle 
	 * circle. The zones are defined as 0-7 (the 8 outer circle), zone 8, inner circle, zone
	 * 9, the middle circle  
	 * 
	 * @param x (x in touchpad coordinates
	 * @param y (y in touchpad coordinates
	 * @return the index of the zone
	 */
	/*private int getZone8(int x, int y, double startingAngle) {

		//1. transform the corner points to the center, and to the screen coordinates
		Point p = new Point(x, y);
		Point p1 = this.touchPadCornerToScreen(p);
		Point p2 = this.screenCornerToCenter(p1);
		x = (int) p2.getX();
		y = (int) p2.getY();

		double radius = ContinuousControl.getLength(new Point(x, y), new Point(
				0, 0));

		if (radius <= INNER_ZONE_THRESHOLD)
			return 8;
		if (radius >= INNER_ZONE_THRESHOLD && radius <= OUTER_ZONE_THRESHOLD)
			return 9;

		//now I need to rotate a little to make the zones correct 

		double tmpX = (Math.cos(startingAngle) * x - Math.sin(startingAngle) * y);
		double tmpY = (Math.sin(startingAngle) * x + Math.cos(startingAngle) * y);

		x = (int) tmpX;
		y = (int) tmpY;

		//otherwise, we know it's in the outer ring 
		if (x >= 0 && y >= 0) {
			//we know it's either zone 0 or 1
			if (x >= y)
				return 0;
			else
				return 1;
		} else if (x <= 0 && y >= 0) {
			//we know it's either zone 2 or 3
			if (Math.abs(x) <= y)
				return 2;
			else
				return 3;
		} else if (x <= 0 && y <= 0) {
			//we know it's zone 4 or 5
			if (Math.abs(x) > Math.abs(y))
				return 4;
			else
				return 5;
		} else if (x >= 0 && y <= 0) {
			//we know it's in z one 6 or 7
			if (x <= Math.abs(y))
				return 6;
			else
				return 7;
		}

		return 9;
	}*/
	
	/**
	 * This methods is not weighted, or every element has the equal weight
	 * so the we pass a null variable tot he weighted array there. 
	 * @param x
	 * @param y
	 * @param startingAngle
	 * @param nElements
	 * @return
	 */
	public int getZoneN(int x, int y, double startingAngle, int nElements){
		return getWeightedZoneN(x,y, startingAngle, nElements, null, 0);
	}
	
	/**
	 * get zone number in the spiral mode. Depends on how many items we allows for each 
	 * circle, we will return the index according how many trips the user has put into
	 * the circling gesture. 
	 * @param x the x position of the finger
	 * @param y the y position of the finger
	 * @param startAngle the starting angle 
	 * @param nItemsPerLevel how many items are allowed per circle
	 * @param zoneList the list of the zones already traveled 
	 * @return the zone index number (could be greater than the total number of elements, but this 
	 * is up to the routine that calls this method to handle. 
	 */
	public int getZoneSpiral(int x, int y, double startAngle, int nItemsPerLevel, ArrayList zoneList){
		
		///we also want to find out the number of items per level
		int numOfItemsThisLevel = 0;
		
		if(moLastItem == null){
			//we are at the root
			numOfItemsThisLevel = moGraphicModel.getLayer(0).size();
		}
		else{
			//check the leaf level 
			numOfItemsThisLevel = moLastItem.getChildCount();
		}
		
		//1. analyze the spiral list to get the current spiral level
		int spiralLevel = analyzeSpiralLevel(zoneList, nItemsPerLevel);
		
		//2. get the zone index if it's not spiral
		int zoneIndex = getZoneN(x, y, startAngle, nItemsPerLevel);
		
		zoneIndex = spiralLevel * nItemsPerLevel + zoneIndex;
		
		if(AUTO_REPEAT){
			zoneIndex = Math.abs(zoneIndex % numOfItemsThisLevel);
		}
		
		//if the zoneIndex is greater than the total number of elements, return the index for the last item 
		return zoneIndex;
	}
	
	/**
	 * Analyze the list of zones this gesture has traveled. If it's 
	 * greater than certain number 
	 * @param zoneList
	 * @param nItemsPerLevel
	 * @return
	 */
	private int analyzeSpiralLevel(ArrayList zoneList, int nItemsPerLevel){
		
		//1. init spiral level
		int spiralLevel = 0;
		
		//2.we assume user needs to go through the full circle to get to the
		//next level, so if the item number is less than number allowed in a circle
		//we are at level 0
		if(zoneList.size() < nItemsPerLevel) return 0;
		
		//3. basically check to see if the number we have sequence (n-1),0, then increase spiral level
		// or if we have sequence 0, (n-1), then decrease spiral level by 1
		for(int i=0; i< zoneList.size(); i++){
			if(i < zoneList.size()-1){
				int num1 = ((Integer)zoneList.get(i)).intValue();
				int num2 = ((Integer)zoneList.get(i+1)).intValue();
				//is going clockwise, and 
				if(num1 == nItemsPerLevel -1 && num2 == 0) spiralLevel ++;
				else if(num1 == 0 && num2 == nItemsPerLevel -1) spiralLevel --;
			}
		}
		
		return spiralLevel;
	}
	

	/**
	 * get the current zone the finger is at if we have arbitrary N elements 
	 * on the same circle. Zone -2 will be center, zone -3 is the space in between
	 * zone -1 will be invalid, and zone 0 - (N-1) will be the zone at the current
	 * position 
	 * 
	 * @param x (x in touchpad coordinates
	 * @param y (y in touchpad coordinates
	 * @param staringAngle the starting angle in radian (change to degree now
	 * @return the index of the zone
	 */
	private int getWeightedZoneN(int x, int y, double startingAngle, int nElements, int[] weights, int startIndex) {

		//1. transform the corner points to the center, and to the screen coordinates
		Point p = new Point(x, y);
		Point p1 = this.touchPadCornerToScreen(p);
		Point p2 = this.screenCornerToCenter(p1);
		x = (int) p2.getX();
		y = (int) p2.getY();

		//2. get the length of the point to the center. 
		double radius = ContinuousControl.getLength(new Point(x, y), new Point(
				0, 0));

		//3. check to see if the length is within the zone -2 or -3 or not. 
		if (radius <= INNER_ZONE_THRESHOLD)
			return -2;
		// -- Take radius offset into account (see hysteresisOffsets[] declaration)
		if (radius >= INNER_ZONE_THRESHOLD && radius <= OUTER_ZONE_THRESHOLD + hysteresisOffsets[0])
			return -3;

		//4. now I need to rotate points according to the staring angle so that 
		//it will reflect the zone within the rotated space 
		double tmpX = (Math.cos(startingAngle) * x - Math.sin(startingAngle) * y);
		double tmpY = (Math.sin(startingAngle) * x + Math.cos(startingAngle) * y);

		x = (int) tmpX;
		y = (int) tmpY;
		
		//4.5 calculate the delta sum 
		float totalSum = 0F;
		
		if(weights == null) totalSum = nElements; //if no weight, the total sum is normal = 360 degrees
		else {
			//we calculate the total weight 
			totalSum = (float) getSumOfIntArray(weights, weights.length);
		}//end of else
		
		//5. need to translate the starting position of the arc from the 0,0 of the pad
		//to the actually location on the pad
		//Basic formula for each arc
		// a. angle start = starting angle + (cumulativeWeight/totalWeight) * 360 degrees
		// b. angle extent = (weight for that element/totalWeight) * 360 degrees
		// however, if the weight is null, we will do it differently
		
		//because the finger sometimes goes outside the circle
		//and we don't want that to happen, I need to add an extension to the radius
		int extension = 20; //the maximum you can go is 20, so we set it to 20
		int extendedRadius = DEBUG_RADIUS + extension;
		
		float arcX = (float) ((DEBUG_WIDTH - 2 * extendedRadius) / 2), arcY = (float) (DEBUG_HEIGHT - 2 * extendedRadius) / 2;
		//float angleStart = (float) Math.toDegrees(startingAngle);
		float angleStart = (float) startingAngle;
		Arc2D arc = new Arc2D.Float(arcX,arcY,(float)2*extendedRadius, (float)2*extendedRadius,0,0,Arc2D.PIE);;
		for(int i= startIndex; i< nElements + startIndex; i++){
			//5. we need to check to see if which zone the point is located at
			//float elementWeight = 360F/nElements; //if the weights is null, we set element weight to 1
			float elementWeight = 1.0F;
			if(weights!=null) elementWeight = weights[i];
			float cumulativeWeight =  ((i+1)*elementWeight); //we set cumulative weight to i
			
			//calculating the total  
			int upTo = 0;
			if(i > nElements-1) upTo = i - nElements;
			if(weights!=null) cumulativeWeight = getSumOfIntArray(weights, startIndex, upTo);
			
			float angleExtent = (elementWeight/totalSum) * 360;
			//the angle extend is the weight of the element divided by the totalSum * 360 degrees
			arc.setAngleExtent(angleExtent);
			//the angle start is the starting angle + the cumulative sum of the element * 360 degrees
			arc.setAngleStart((float)(angleStart - (cumulativeWeight/totalSum) * 360));
			
			// -- Take angle offsets into account (see hysteresisOffsets[] declaration)
			float start_offset = hysteresisOffsets[i+1];
			float end_offset = hysteresisOffsets[i==0?nElements:i];
			arc.setAngleStart(arc.getAngleStart() + start_offset);
			arc.setAngleExtent(arc.getAngleExtent() - start_offset + end_offset);
			
			if(arc.contains(p1.getX(), p1.getY())){
				return i;
			}
		}
		return -1;
	}
	
	//======================================================
    //TODO: End of handling techniques, start of drawing methods
    //======================================================

	/**
	 * Draw the arcs of the circle, and try to see them in action in debugging 
	 * @param startingAngle in degrees now
	 * @param nElements
	 * @param weights
	 * @param gg
	 */
	private void drawArcs(int zoneIndex, double startingAngle, int nElements, int[] weights, Graphics2D gg){
		gg.setColor(Color.black);
		
		//4.5 calculate the delta sum 
		float totalSum = 0F;
		
		if(weights == null) totalSum = nElements; //if no weight, the total sum is normal = 360 degrees
		else {
			//we calculate the total weight 
			totalSum = (float) getSumOfIntArray(weights, weights.length);
		}//end of else
		int extension = 20; //the maximum you can go is 20, so we set it to 20
		int extendedRadius = DEBUG_RADIUS + extension;
		
		float arcX = (float) ((DEBUG_WIDTH - 2 * extendedRadius) / 2), arcY = (float) (DEBUG_HEIGHT - 2 * extendedRadius) / 2;
		float angleStart = (float) startingAngle;
		Arc2D arc = new Arc2D.Float(arcX,arcY,(float)2*extendedRadius, (float)2*extendedRadius,0,0,Arc2D.PIE);;
		for(int i=0; i< nElements; i++){
			//5. we need to check to see if which zone the point is located at
			//float elementWeight = 360F/nElements; //if the weights is null, we set element weight to 1
			float elementWeight = 1.0F;
			if(weights!=null) elementWeight = weights[i];
			float cumulativeWeight =  ((i+1)*elementWeight); //we set cumulative weight to i
			if(weights!=null) cumulativeWeight = getSumOfIntArray(weights, i);
			
			float angleExtent = (elementWeight/totalSum) * 360;
			//the angle extend is the weight of the element divided by the totalSum * 360 degrees
			arc.setAngleExtent(angleExtent);
			//the angle start is the starting angle + the cumulative sum of the element * 360 degrees
			arc.setAngleStart((float)(angleStart - (cumulativeWeight/totalSum) * 360));
			
			// -- Take angle offsets into account (see hysteresisOffsets[] declaration)
			float start_offset = hysteresisOffsets[i+1];
			float end_offset = hysteresisOffsets[i==0?nElements:i];
			arc.setAngleStart(arc.getAngleStart() + start_offset);
			arc.setAngleExtent(arc.getAngleExtent() - start_offset + end_offset);
			
/*			if(i  == zoneIndex){
				gg.setColor(Color.GREEN);
			}
			else*/
			gg.setColor(new Color(i%3/2f, 0, 1-i%2/1f));
			
			gg.draw(arc);
		}
		
		// Draw inner circle
		// -- Take radius offset into account (see hysteresisOffsets[] declaration)
		drawCircle(gg, OUTER_ZONE_THRESHOLD + hysteresisOffsets[0]);
	}
	
	private void drawCircle(Graphics2D g, double radius) {
		float arcX = (float) ((DEBUG_WIDTH - 2 * radius) / 2), arcY = (float) (DEBUG_HEIGHT - 2 * radius) / 2;
		g.setColor(Color.white);
		g.fillOval((int)(arcX), (int)(arcY), (int)(2*radius), (int)(2*radius));
		g.setColor(Color.black);
		g.drawOval((int)(arcX), (int)(arcY), (int)(2*radius), (int)(2*radius));
	}

    //Screen Coordination and angle calculation

    //Methods for drawing 
	/**
	 * I will try to draw the zones for 
	 * @param g
	 * @param startingAngle
	 */
	public void drawZones(Graphics2D g, double startingAngle) {

		//1. draw the three circles as the interaction space
		int radius = DEBUG_RADIUS;
		int x = (DEBUG_WIDTH - 2 * radius) / 2;
		int y = (DEBUG_HEIGHT - 2 * radius) / 2;
		//Point circle1Point = new Point(x, y);
		g.drawOval(x, y, 2 * radius, 2 * radius);

		drawCompass8(new Point(DEBUG_WIDTH / 2, DEBUG_HEIGHT / 2), DEBUG_RADIUS,
				Math.PI / 8.0, g);
		//2. draw the lines to separate the zones from each other. 
		radius = (int) OUTER_ZONE_THRESHOLD;
		x = (DEBUG_WIDTH - 2 * radius) / 2;
		y = (DEBUG_HEIGHT - 2 * radius) / 2;
		//circle1Point = new Point(x, y);

		g.setColor(Color.WHITE);
		g.fillOval(x, y, 2 * radius, 2 * radius);
		g.setColor(Color.BLACK);
		g.drawOval(x, y, 2 * radius, 2 * radius);

		radius = (int) INNER_ZONE_THRESHOLD;
		x = (DEBUG_WIDTH - 2 * radius) / 2;
		y = (DEBUG_HEIGHT - 2 * radius) / 2;
		//circle1Point = new Point(x, y);

		g.setColor(Color.WHITE);
		g.fillOval(x, y, 2 * radius, 2 * radius);
		g.setColor(Color.BLACK);
		g.drawOval(x, y, 2 * radius, 2 * radius);
		
	}

	String[] COMPASS_8 = { "3", "4", "5", "6", "7", "0", "1", "2" };
	private boolean BeforeFirstVisualMarkingCondition;
	private boolean BeforeFirstVisualPulldownCondition;
	

	private void drawCompass8(Point center, int radius, double startAngle,
			Graphics2D gg) {
		//G.debug("draw compass 8");

		Font font = new Font("SansSerif", Font.BOLD, 24);
		/*if (dynamicFont) {

		 if (miScreenSize == 0)
		 font = new Font("SansSerif", Font.BOLD, 24);
		 else if (miScreenSize == 1) {
		 font = new Font("SansSerif", Font.BOLD, 14);
		 } else if (miScreenSize == 2) {
		 font = new Font("SansSerif", Font.BOLD, 8);
		 }
		 }*/
		FontMetrics fmetric = getFontMetrics(font);
		// current font metrics
		float width; // width is variable
		float height = fmetric.getHeight(); // height is constant

		int num = 8;
		Point[] points = new Point[num];
		double currentAngle = startAngle;
		double stepAngle = Math.PI / 4.0;
		for (int i = 0; i < num; i++) {
			points[i] = new Point((int) (center.x + Math.cos(currentAngle)
					* radius), (int) (center.y + Math.sin(currentAngle)
					* radius));
			currentAngle += stepAngle;
		}
		gg.setFont(font);

		for (int i = 0; i < num; i++) {
			gg.drawLine(points[i].x, points[i].y, center.x, center.y);
			width = fmetric.stringWidth(COMPASS_8[i]);
			switch (i) {
			case 0: //e
				gg.drawString(COMPASS_8[i], points[i].x, points[i].y + height
						/ 3.0f);
				break;
			case 1: //se
				gg.drawString(COMPASS_8[i], points[i].x, points[i].y + height
						/ 3.0f);
				break;
			case 2: //s
				gg.drawString(COMPASS_8[i], points[i].x - width / 2,
						points[i].y + height / 2.0f);
				break;
			case 3: //sw
				gg.drawString(COMPASS_8[i], points[i].x - width / 2.0f,
						points[i].y + height / 2.0f);
				break;
			case 4: //w
				gg.drawString(COMPASS_8[i], points[i].x - width, points[i].y
						+ height / 3.0f);
				break;
			case 5: //nw
				gg.drawString(COMPASS_8[i], points[i].x - width / 2.0f,
						points[i].y);
				break;
			case 6: //n
				gg.drawString(COMPASS_8[i], points[i].x - width / 2,
						points[i].y);
				break;
			case 7: //ne
				gg.drawString(COMPASS_8[i], points[i].x, points[i].y);
				break;
			}

		}
	}

	public void paintComponent(Graphics gg) {
		if ((!DEBUG) && (!DEMO_MODE)) return;
		
		Graphics2D g = (Graphics2D) gg;
		g.scale(DEBUG_ZOOM, DEBUG_ZOOM);
		
		//need to paint the text messages on the top, and see how that works. 
		
		if (PaintStatus == CLEAR_SCREEN) {
			g.clearRect(0, 0, DEBUG_WIDTH, DEBUG_HEIGHT);
			//drawZones(g, mdStartAngle);
			drawArcs(0, mdStartAngle, miCurrentMenuBreadth, null, g);
		}

		g.setColor(Color.BLUE);
		float strokeThickness = 2.0f;
		BasicStroke stroke = new BasicStroke(strokeThickness);
		g.setStroke(stroke);

		if (moGesture != null && PaintStatus == PAINT_STROKE) {
			drawGesture(moGesture, g);
		} else if (moGesture != null && PaintStatus == PAINT_STROKE_COMPONENT) {
			
			ArrayList pressures = moGesture.getPressureValues();
			Point p1 = this.touchPadCornerToScreen(new Point(miX, miY));
			Point p2 = this.touchPadCornerToScreen(new Point(miLastX, miLastY));
			int pressure = ((Integer)pressures.get(moGesture.size()-1)).intValue();
			Color c;
			if (pressure >= 64) {
				c = Color.red;
			} else {
				c = Color.blue;
			}
			gg.setColor(c);
			
			g.clearRect(0, 0, DEBUG_WIDTH, DEBUG_HEIGHT);
			drawArcs(0, mdStartAngle, miCurrentMenuBreadth, null, g);

			strokeThickness = 10.0f;
			stroke = new BasicStroke(strokeThickness);
			g.setStroke(stroke);
			//gg.drawLine(point1[0], point1[1], point2[0], point2[1]);
			gg.drawLine((int) p1.getX(), (int) p1.getY(), (int) p2.getX(),
					(int) p2.getY());
			//g.drawLine(miX, miY, miLastX, miLastY);
		}
	}

	/**
	 * Draw the gesture only, used to show the path of the mouse during drawing
	 * 
	 * @param gest
	 * @param gg
	 */
	public void drawGesture(Gesture gesture, Graphics2D gg) {
		TimedPolygon tp = gesture.getPoints();
		ArrayList pressures = gesture.getPressureValues();

		int[] xs = tp.xpoints;
		int[] ys = tp.ypoints;

		for (int i = 0; i < tp.npoints - 1; i++) {
			//G.debug("draw line");
			int pressure = ((Integer)pressures.get(i)).intValue();
			Color c;
			if (pressure >= 64) {
				c = Color.red;
			} else {
				c = Color.blue;
			}
			gg.setColor(c);
			Point p1 = this.touchPadCornerToScreen(new Point(xs[i], ys[i]));
			Point p2 = this.touchPadCornerToScreen(new Point(xs[i + 1],
					ys[i + 1]));

			//gg.drawLine(point1[0], point1[1], point2[0], point2[1]);
			gg.drawLine((int) p1.getX(), (int) p1.getY(), (int) p2.getX(),
					(int) p2.getY());
		}
	}
	
	//==========================================================
	//TODO: Where I want to insert the experiment implementation code
	//==========================================================
	
	//	sound initialization and menu initialization
	// dependencies: 
	//      resetSound (will call initSoundAndMenu which will reset everything) 
	//          ---> initSoundAndMenu <---
	//                  buildUISound
	//                  formatComponent
	//                        formatSubComponent
	
	/**
	 * This is part of the initialization. All the soundsets
	 * will be initialized in formatComponent and subcomponents, and 
	 * stored in the hashmap
	 *
	 */
	  private void initSoundAndMenu(ExperimentCondition condition) {
        //==>< First, configure JASS.
        SoundManager.configure(soundAPI, sampleRate, bufferSize, bufferSizeRender);
        
        //==> still need to initialize the UI thing
        UISounds = buildUISoundSet(UIFiles, condition);
        
        //==> press/release sounds
        UISounds2 = new SoundSet("UISounds2", UIFiles2.length);
        for (int i=0; i<UIFiles2.length; i++) {
        	float vol = 0.3f;
        	if (i==3) vol = 1.5f;
        	if (i==6) vol = 0.8f;
            UISounds2.setSample(i, getFileName(getPrefix(ANDREW) + UIFiles2[i]), 0, 0, vol);
        }
        UISounds2.setBlending(5);

        if (CommandSounds == null) {
        	//==> set up the command sound
        	CommandSounds = new SoundSet("CommandSounds", 10);
        		for (int index=0; index<Commands.length; index++) {
        			// locations are: 0=N, 1=NE, 2=E, 3=SE, 4=S, 5=SW, 6=W, 7=NW
        			CommandSounds.setSample(index, Commands[index], 0, 0);
        		}
            CommandSounds.setVolume(1.0f);
            CommandSounds.setBlending(1);
        }

        if (NumberSounds == null) {
        	//==> initialize number sound
	        NumberSounds = new SoundSet("NumberSounds", 29);
	        	for(int i=0; i < Numbers.length; i++){
	        		NumberSounds.setSample(i, Numbers[i], 0, 0);
	        	}
	 
	        NumberSounds.setVolume(0.8f);
        }

        if (ResponseSounds == null) {
	        //==> Set up response sound
	        ResponseSounds = new SoundSet("ResponseSounds", Responses.length);
	         	for(int i=0; i < Responses.length; i++){
	        		ResponseSounds.setSample(i, Responses[i], 0, 0);
	        	}
	        ResponseSounds.setVolume(1.0f);
	        ResponseSounds.setBlending(1);
        }
	  
        boolean addingSound = true;
        boolean addingVisual = true;
        boolean spatialize = true;
        boolean limitedlistmode = true;
        //==> format the component and establish the soundset for them
        
     if(!moCurrentCondition.AUDIO_STIMULI) addingSound = false;
      /*  if(miCurrentExperimentMode == ExperimentCondition.VisualPulldownMenu 
        		|| miCurrentExperimentMode == ExperimentCondition.VisualMarkingMenu
        		|| miCurrentExperimentMode == ExperimentCondition.VisualSpiralLongMenu
        		|| miCurrentExperimentMode == ExperimentCondition.VisualOrthoZoomLongMenu
        		|| miCurrentExperimentMode == ExperimentCondition.VisualSpeedDependentLongMenu) addingSound = false; 
  */      if(miCurrentExperimentMode == ExperimentCondition.AudioPulldownMenu ||
        		miCurrentExperimentMode == ExperimentCondition.AVPulldownMenu 
        		|| miCurrentExperimentMode == ExperimentCondition.AudioSpiralLongMenu
        	        		) spatialize = false; 
        /*if(miCurrentExperimentMode == ExperimentCondition.EarPodMenu 
        		|| miCurrentExperimentMode == ExperimentCondition.AudioSpiralLongMenu
        		|| miCurrentExperimentMode == ExperimentCondition.AudioOrthoZoomLongMenu
        		|| miCurrentExperimentMode == ExperimentCondition.AudioSpeedDependentLongMenu
        		) addingVisual = false; */
     if(!moCurrentCondition.VISUAL_STIMULI)addingVisual = false;
        
        if(conditionToMode(moCurrentCondition)== SPIRAL_MODE || conditionToMode(moCurrentCondition) == SPEEDZOOM_MODE		
        		||conditionToMode(moCurrentCondition)== ORTHOZOOM_MODE) limitedlistmode = false; 
        
        
        ///for audiovisualmarking menu, nothing is false, so no if statement here for this condition
        
        formatComponent(condition.miBreadth, condition.miDepth, addingSound, spatialize, addingVisual, limitedlistmode);
        
        // Launch JASS player.
        SoundManager.init();
    }
	  
	  	//	 dependencies: 
		//      resetSound (will call initSoundAndMenu which will reset everything) 
		//          	initSoundAndMenu 
		//               --->buildUISound <---
		//                  formatComponent
		//                        formatSubComponent
	  
	  private SoundSet buildUISoundSet(String[] files, ExperimentCondition condition) {
        int numberOfSounds = 22;
        //int playingChannels = 5; // UI sounds are additive
        SoundSet UISounds = new SoundSet("UISounds", numberOfSounds);
        
        //Set two additional sounds
        UISounds.setSample(0, getFileName(getPrefix(ANDREW) + files[1]), 0, 0);
        UISounds.setSample(1, getFileName(getPrefix(ANDREW) + files[2]), 0, 0);
        
        //so I am distinguishing between compact_mode and the other mode 
        if (CurrentMode != COMPACT_MODE) {
			// Generate eight spatialized wheel clicks
			for (int index = 0; index < condition.miBreadth; index++) {
				float location = indexToLocation(index, condition.miBreadth);
				// locations are: 0=N, 1=NE, 2=E, 3=SE, 4=S, 5=SW, 6=W, 7=NW
				UISounds.setSample(index + 2, getFileName(getPrefix(ANDREW) +files[0]), location, 0);
			}
		} else {
			//we know it's compact mode,
			for (int index = 0; index < 20; index++) {
				//try to generate the linear sound
				float location = 2.0F + (index / 19.0F) * 4.0F;
				UISounds.setSample(index + 2, getFileName(getPrefix(ANDREW) +files[0]), location, 0);
			}
		}
        UISounds.setVolume(0.6f);
        UISounds.setBlending(4);
        return UISounds;
    }
	  
	  
	  /**
		 * Do the formating of the Sunburst menu component this need to be taken out
		 * of the paintComponent method because this will be only done once, and we
		 * don't want to calculate this everything when repaint is called
		 * 
		 * Version 2 of format: somehow I need to have the formatting done in way
		 * that I can open a crack for the circle to show everything a little
		 * better.
		 * 
		 * openingFactor determines how are we going to render the rest of the
		 * circle. for openingfactor = 2.0, it's a full circle, if it's 1.0 it's a
		 * half circle. I think I want to open 45 degrees so it's 1.5 and the
		 * startRadian determines where the opening will be
		 * 
		 * breadth and depth is the limiting factors here. For example, the tree
		 * can be breadth12 depth2, but we can limit this tree to breadth8, depth 1
		 * in this case, when we format the component, only the first 8 items from
		 * level 1 will be considered, the rest will be skipped. 
	 * @param limitedlistmode if it's false means we are dealing with long list here, otherwise
	 *                        it's a standard limited-sized menu, such as marking menu
		 */
		@SuppressWarnings("unchecked")
		public void formatComponent(int breadth, int depth, boolean addingSound, boolean spatialize, boolean addingVisual, boolean limitedlistmode) {

			//G.debug("format component called", 0);
			//1. initialize moGraphicModel if null
			if (moGraphicModel == null)
				moGraphicModel = new TreeAudibleModel(moList, null);

			//2. get its item count
			int numItems = this.getItemCount();
			if(breadth!= -1 && numItems > breadth) numItems = breadth;
			
			TreeModel item;
			
			//3. get the depth
			if(depth == -1)
				depth = moGraphicModel.getMaxDepth();

			//4. need to set the id for each node, this id will later be used to retrieve
			//the soundset file 
			String id = "0";
			
			//setup the visual element string array
			String[] visualElements = new String[numItems];
			
			//initialize the soundset
			//1. a soundset for the stimulus (since we are using
			//sound for stimulus, we always need a set of sound) 
			SoundSet stimulusSet = new SoundSet("stimulusSet", numItems);
			
			//2. soundset for the audio menu 
			SoundSet soundset = null;
			if(addingSound) {
				soundset = new SoundSet("audioMenu", numItems+1);
				// the following will allow sounds to stop more smoothly
				soundset.setBlending(2, 0.5f, 0f, 0.1f);
			}

			for (int i = 0; i < numItems; i++) {
				//// 1.1.1. Calculate the arc size.
				AudibleMenuItem mItem = new AudibleMenuItem();
				//mItem.setVisible(true);
				mItem.setPosition(i);
				mItem.setID(id+i); //accumulate the id for the menu item 
				item = moList.getChild(i);
				mItem.setText(item.getTitle());
				mItem.setSoundFile(item.getSoundFile());

				moGraphicModel.addShapeToLayer(0, mItem);
				//no matter adding sound or not, we will build a set of sound for stimulus
				//and for the stimulus, the sound is not spatialized. 
				if (!SKIP_AUDIO_LOAD) {
					if(limitedlistmode)
						stimulusSet.setSample(i, mItem.getSoundFile(getPrefix(ANDREW)), 0, optiondelay);
					else
						stimulusSet.setSample(i, mItem.getSoundFile(getPrefix(LONGLIST)), 0, optiondelay);
				}
				if(addingSound && !SKIP_AUDIO_LOAD){
					float location = indexToLocation(i, breadth);
					if (!spatialize) {
						location = 0;
					}
					
					if(limitedlistmode)
						soundset.setSample(i, mItem.getSoundFile(getPrefix(PAULA)), location, optiondelay);
					else
						soundset.setSample(i, mItem.getSoundFile(getPrefix(LONGLIST)), location, optiondelay);
				}
				visualElements[i] = item.getName();
				if (item.childCount() > 0) {
					formatSubMenu(item, mItem, breadth, depth - 1, addingSound, spatialize, addingVisual, limitedlistmode);
				}
			} // of for
			
			// add "no item" sound
			if (addingSound && !SKIP_AUDIO_LOAD)
				soundset.setSample(numItems, getPrefix(PAULA) + "\\no-item.wav", 0, 0);
			
			moSoundSetMap.put(id, soundset);
			moStimulusSoundMap.put(id, stimulusSet);
			moItemNamesMap.put(id, visualElements);
		}
		
		@SuppressWarnings("unchecked")
		public void formatSubMenu(TreeModel list, AudibleMenuItem parent, int breadth, int depth, boolean addingSound, boolean spatialize, boolean addingVisual, boolean limitedlistmode) {

			if(depth < 1) return;
			
			int subMenuSize = list.childCount();
			if(breadth!= -1 && subMenuSize > breadth) subMenuSize = breadth;
			
			int index = moGraphicModel.getMaxDepth() - depth;
			
			//initialize the soundset
			//1. a soundset for the stimulus (since we are using
			//sound for stimulus, we always need a set of sound) 
			SoundSet stimulusSet = new SoundSet("subMenuStimulusSet", subMenuSize);
			
			//2. soundset for the audio menu 
			SoundSet soundset = null;
			//setup the visual element string array
			String[] visualElements = new String[subMenuSize];
			
			//==> special treatment for audio menus
			if(addingSound) {
				soundset = new SoundSet("audioSubMenu", subMenuSize+1);
				//	the following will allow sounds to stop more smoothly
				soundset.setBlending(2, 0.5f, 0f, 0.1f);
			}
			
			String id = parent.getID();
			
			for (int i = 0; i < subMenuSize; i++) {
				AudibleMenuItem mItem = new AudibleMenuItem();
				mItem.setPosition(i);
				TreeModel subItem = list.getChild(i);
				//temporary hack
				mItem.setText(subItem.getTitle());
				mItem.setID(id+i);
				mItem.setSoundFile(subItem.getSoundFile());
				parent.addChild(mItem);
				moGraphicModel.getLayer(index).add(mItem);	  
				
				if (!SKIP_AUDIO_LOAD) {
					if(limitedlistmode)
						stimulusSet.setSample(i, mItem.getSoundFile(getPrefix(ANDREW)), 0, optiondelay);
					else
						stimulusSet.setSample(i, mItem.getSoundFile(getPrefix(LONGLIST)), 0, optiondelay);
				}
				
				//==> special treatment for audio menus
				//Basically add set the soundset there
				//int location = i%8;
				if(addingSound && !SKIP_AUDIO_LOAD){
					float location = indexToLocation(i, breadth);
					if (!spatialize) location = 0;
					if(limitedlistmode)
						soundset.setSample(i, mItem.getSoundFile(getPrefix(PAULA)), location, optiondelay);
					else
						soundset.setSample(i, mItem.getSoundFile(getPrefix(LONGLIST)), location, optiondelay);
				}
				visualElements[i] = subItem.getName();
				
				//these code will put the sound in a linear line
				/*if(CurrentMode != COMPACT_MODE){
					//we try to format it in 8 directions if it's not compact mode 
					int location = i%8;
					soundset.setSample(i, mItem.getSoundFile(), location, optiondelay);
				}
				else{
					float location = 2.0F + ((float)i / (subMenuSize-1)) * 4.0F;
					soundset.setSample(i, mItem.getSoundFile(), location, optiondelay);
				}*/
				
				if (subItem.childCount() > 0) {
					formatSubMenu(subItem, mItem, breadth, depth - 1, addingSound, spatialize, addingVisual, limitedlistmode);
				}
			}
			
			// add "no item" sound
			if (addingSound && !SKIP_AUDIO_LOAD)
				soundset.setSample(subMenuSize, getPrefix(PAULA) + "\\no-item.wav", 0, 0);

			moSoundSetMap.put(id, soundset);
			moStimulusSoundMap.put(id, stimulusSet);
			moItemNamesMap.put(id, visualElements);
		}
		
	/**
	 * reset the sound source, so it can adapt to 
	 * different menu breadth and conditions
	 * For example, for menu with breadth 8, the ui sound
	 * will be different from menus with breadth 12. 
	 *
	 */
	protected void resetSound(ExperimentCondition condition, boolean firstTime){
		//if(firstTime != true)
		//	SoundManager.quit();
		
		initSoundAndMenu(condition);
	}
	
	
	//===> set up the visual menu
	/**
	 * Setup the visual menu. Steps involve 
	 * 1. Setup visual 
	 */
//	private void SetupVisualMenu(ExperimentCondition condition){
//		//formatComponent(condition.miBreadth, condition.miDepth, addingAudio);
//		if(moVisualList == null)
//			moVisualList = new JList();
//	}
	
	
	//dependency
	//		PlayNumber(int)
	//				translateNoToVoice(int)
	//				PlayNumber(int[] sequence)
	
	/**
	 * Play the number for a trial. Uses translateNoToVoice(int) 
	 * and PlayNumber(int[] sequence);
	 * 
	 * @param number
	 * @return
	 */
	public long PlayNumber(int number){
		if(number < 0 && number > 100) return 0;
		int[] sequence = TranslateNoToVoice(number);
		long timeSpan = PlayNumber(sequence);
		return timeSpan;
	}
	
	
	/**
	 * uses playStimuliItem, and selectItemForSimuli
	 */
	public void PlayStimulus(final Stimulus stimulus){
	
		//Thread.dumpStack();

		mlStartStimulus = Logger.getTime();

		if (moCurrentCondition.AUDIO_STIMULI) {
			// in all cases play stimulus asynchronously, to be able to detect user's reaction
			Thread playstimulusthread = new Thread() {
				public void run() {
					playStimulusSync(stimulus);
				}
			};
			stopPlayingStimulus = false;
			playstimulusthread.start();
			// in asynchronous stimulus/response mode, we consider stimulus  
			// is already finished.
			if (moCurrentCondition.AUTOMATIC_STIMULI) {
				mlFinishStimulus = Logger.getTime();
			}
		} else {
			// show stimulus synchronously
			playStimulusSync(stimulus);			
		}
	}
	
	public void playStimulusSync(final Stimulus stimulus) {
		final String[] stimuli = stimulus.getStimuli();
		AudibleMenuItem parent = null;
		currentLevelID = "0";

		// fill the visual menu
		if (moCurrentCondition.AUTOMATIC_STIMULI) {
			if(conditionToMode(moCurrentCondition) == PULLDOWN_MODE){
				String[] items = (String[]) moItemNamesMap.get("0");
				moVisualList.setForeground(Color.BLACK);
				moVisualList.setListData(items);
			}
			else if(miCurrentExperimentMode == ExperimentCondition.VisualMarkingMenu ||
					miCurrentExperimentMode == ExperimentCondition.AVEarPodMenu){
				String[] items = (String[]) moItemNamesMap.get("0");
				moVisualMarkingPanel.setForeground(Color.BLACK);
				moVisualMarkingPanel.setMenuItems(items);
				moVisualMarkingPanel.setSelectedIndex(-1); //set the index to empty at the starting point
				
			}//end of else if
		}

		// display visual stimulus
		if(moCurrentCondition.VISUAL_STIMULI) {
			//String[] stimuliStrs = moCurrentStimulus.getStimuli();
			if(DEMO_MODE){
				setVisualMessage("Try it out: select the following menu item using your earPod device", 1, Color.red, TextPosition.NORTH);
			}
			
			setVisualMessage(StimuliToVisualText(stimuli), 0, Color.green);
		}			

		// play audio stimulus
		if(moCurrentCondition.AUDIO_STIMULI) {

			setStimuliVolume(1);
			
			for(int i=0; i< stimuli.length; i++){
				
				String str = stimuli[i];
				int index = 0;
				
				try{
					index = Integer.parseInt(str);
				}catch(Exception e){
					index = 0;
				}
			
				SoundSet sSet = null;
				if(parent != null)
					sSet = (SoundSet) moStimulusSoundMap.get(parent.getID());
				else
					sSet = (SoundSet) moStimulusSoundMap.get("0");
	
				// play stimuli part
				sSet.playWhenEmpty(index);
				long durationL = (long) (sSet.getDuration(index) * 1000);
				try{
					for (int j=0; j<durationL/100 - (i==stimuli.length-1 ? 2 : 1); j++) {
						Thread.sleep(100);
						if (stopPlayingStimulus) {
							sSet.stopAll();
							return;
						}
					}
				} catch (Exception e){}
				
				parent =  selectStimuliItem(index, parent);	
			}
			
			if (stopPlayingStimulus) {
				return;
			}

			if (!moCurrentCondition.AUDIO_STIMULI) {
				//we play a sound to indicate the stimuli is finished
				UISounds2.play(3);
			}
		}
		
		// STIMULUS FINISHED

		// if response must follow stimulus, set trial to start
		// in the other case, user might already triggered that state
		if (!moCurrentCondition.AUTOMATIC_STIMULI) {
			setExperimentStatus(TRIAL_START);
		}

		// fill the visual menu
		if (!moCurrentCondition.AUTOMATIC_STIMULI) {
			if(conditionToMode(moCurrentCondition) == PULLDOWN_MODE){
				String[] items = (String[]) moItemNamesMap.get("0");
				moVisualList.setForeground(Color.BLACK);
				moVisualList.setListData(items);
			}
			else if(miCurrentExperimentMode == ExperimentCondition.VisualMarkingMenu ||
					miCurrentExperimentMode == ExperimentCondition.AVEarPodMenu){
				String[] items = (String[]) moItemNamesMap.get("0");
				moVisualMarkingPanel.setForeground(Color.BLACK);
				moVisualMarkingPanel.setMenuItems(items);
			}
		}
		
		mlFinishStimulus = Logger.getTime();
	}
	
	public void setStimuliVolume(float vol) {
		for (Iterator i = moStimulusSoundMap.values().iterator(); i.hasNext();) {
			((SoundSet)i.next()).setVolume(vol);
		}
	}
	
	public void SelectVisualPulldownItem(){
		
		//==> 1. get the selected index
		int currentIndex = moVisualList.getSelectedIndex();
		//int currentIndex = selectedIndex;
		String[] nextLevel = (String[]) moItemNamesMap.get(msVisualParentID + currentIndex);
		
		//==> 2. check to see if the selected index is a leaf node or not
		//we will check to see if we are at the leave level or not			
		//if the node has no submenu associated, or only one item submenu, we know it's a leaf node
		if(nextLevel == null || nextLevel.length <=1){
			//leaf node
			//for a leaf node, we will reset the parentID, and display the response for the experiment
			//==>2.x add the current response to the response array
			maCurrentResponse.add(currentIndex + "");
			
			//==> All these can be encapulated into EndTrial. So I need to have a 
			// end visual trial, and an end audio trial
			//==>2.x compare the stimulus with the user's response
			//compare the result, basically, we can compare the stimulus with the id, since they are somewhat identical
			EndTrial(false);
			//==>2.x Need to display the message, and display a message to ask the user to tap the screen again 
			
			//==>2.x set the parentID to the root
			msVisualParentID = "0";
		}
		else{
			//not a leaf node
			//2.x: store the user selection into response array
			maCurrentResponse.add(currentIndex+"");
		
			//2.x: set the content 
			moVisualList.setListData(nextLevel);
			//moVisualList.setSelectedIndex(0);
			repaint();
			
			//2.x: set the visualparentID
			msVisualParentID = msVisualParentID + currentIndex;
		}
	}//end of method
	
	private void SelectVisualMarkingItem() {
		
//		==> 1. get the selected index
		int currentIndex = moVisualMarkingPanel.getSelectedIndex();
		//int currentIndex = selectedIndex;
		String[] nextLevel = (String[]) moItemNamesMap.get(msVisualParentID + currentIndex);
		
		//==> 2. check to see if the selected index is a leaf node or not
		//we will check to see if we are at the leave level or not			
		//if the node has no submenu associated, or only one item submenu, we know it's a leaf node
		if(nextLevel == null || nextLevel.length <=1){
			//leaf node
			//for a leaf node, we will reset the parentID, and display the response for the experiment
			//==>2.x add the current response to the response array
			maCurrentResponse.add(currentIndex + "");
			
			//==> All these can be encapulated into EndTrial. So I need to have a 
			// end visual trial, and an end audio trial
			//==>2.x compare the stimulus with the user's response
			//compare the result, basically, we can compare the stimulus with the id, since they are somewhat identical
			EndTrial(false);
			//==>2.x Need to display the message, and display a message to ask the user to tap the screen again 
			
			//==>2.x set the parentID to the root
			msVisualParentID = "0";
		}
		else{
			//not a leaf node
			//2.x: store the user selection into response array
			maCurrentResponse.add(currentIndex+"");
		
			//2.x: set the content 
			moVisualMarkingPanel.setMenuItems(nextLevel);
			repaint();
			
			//2.x: set the visualparentID
			msVisualParentID = msVisualParentID + currentIndex;
		}
	}//end of method
	
	/*
	void setVisualMessage(String message) {
		setVisualMessage(message, Color.black);
	}
	
	void setVisualMessage(String message, Color color) {
		if (iPodLabel != null) {
			iPodLabel.setForeground(color);
			iPodLabel.setText(" " + message);
		}
	}*/
	
	private void StartExperiment(ExperimentCondition experiment){
	
		//prepare the trials 
		ExperimentBuilder audioExperimentBuilder = new ExperimentBuilder(experiment);
		
		moConditionList = audioExperimentBuilder.GetExperimentConditions(miParticipantID);
		moExperimentSet = audioExperimentBuilder.GetExperiment(miParticipantID);
       
		moNavigator.updateConditionList(moConditionList, moExperimentSet);
		
		// Create progress bar and generate experiment summary on std output.
		moProgress = new JExperimentProgress(this);
		if (SUMMARY_ONLY) {
			terminateTouchPad();
			System.exit(0);
		}
		
        moCurrentCondition = (ExperimentCondition) moConditionList.get(miConditionIndex);
        moCurrentInstructionSet = (ArrayList) moExperimentSet.get(moCurrentCondition.toString());
        
        SetExperimentUnderCondition(moCurrentCondition);

		moProgress.setVisible(moCurrentCondition.SHOW_PROGRESS);
		if(!DEMO_MODE)
			WaitForKeyPress("Press space bar to start");
	    moProgress.setVisible(false);
	    
	    if (moCurrentCondition.miTypeOfMenu == ExperimentCondition.VisualPulldownMenu ||
	    		//(AUDIOVISUAL_MENU && miCurrentExperimentMode == ExperimentCondition.AudioPulldownMenu)) {
	    		moCurrentCondition.miTypeOfMenu == ExperimentCondition.VisualMarkingMenu ||
	    		moCurrentCondition.miTypeOfMenu == ExperimentCondition.AVPulldownMenu ||
	    		moCurrentCondition.miTypeOfMenu == ExperimentCondition.AVEarPodMenu
	    		) {
			moVisualFrame.setVisible(true);
			moVisualFrame.toFront();
	    }
	    else{
	    	if(moVisualFrame!=null){
	    		moVisualFrame.setVisible(false);
	    		moVisualFrame.toBack();
	    	}//end of if
	    }//end of else

	    // Welcome message
    	if (moCurrentCondition.AUTOMATIC_STIMULI) {
    	} else {
		    if (moCurrentCondition.VISUAL_STIMULI) {
		    	setVisualMessage("Press the touchpad center to start", 0, Color.white);
		    }
		    if (moCurrentCondition.AUDIO_STIMULI) {
		    	CommandSounds.play(INIT_COMMAND); // welcome to our experiment, press...
		    	WaitToFinish(CommandSounds, INIT_COMMAND);
		    }
    	}
        
//	    if (moCurrentCondition.miTypeOfMenu == ExperimentCondition.VisualPulldownMenu||
//				miCurrentExperimentMode == ExperimentCondition.AudioPulldownMenu) {
//	    }
	    
    	setExperimentStatus(BEFORE_TRIAL);
        miTrialIndex = 0;
		moNavigator.setEnabled(true);

    	// Play stimulus automatically
	    if (moCurrentCondition.AUTOMATIC_STIMULI) {
        	sleep(1000);
	    	handleStartStimulusEvent();
	    }
	}

	
	private void SetExperimentUnderCondition(ExperimentCondition condition){
		
		setVisualMessage("Please wait...", 1, Color.black);

		moProgress.setCurrentCondition(miConditionIndex);
        moProgress.setCurrentTrial(0);
		moNavigator.setCurrentCondition(miConditionIndex);
		
		//set experiment under condition should setup the parameters according 
		//to the condition, but these parameters are not clear yet, so leave this
		//method for blank for now
		miCurrentMenuBreadth = condition.miBreadth;
		miCurrentMenuDepth = condition.miDepth; 
		miCurrentExperimentMode = condition.miTypeOfMenu;
		
		///========================================================
		///I also need to load the content for this condition here.
		///=========================================================
		SaxSunburstLoader ssl = new SaxSunburstLoader();
		ssl.loadMenus(condition.msContent);
		
		TreeModel m1 = ssl.getDefaultTreeModel();

		ArrayList list = new ArrayList();

		for (int i = 0; i < m1.getChild(0).childCount(); i++) {
			list.add(m1.getChild(0).getChild(i));
		}

		moFullList = list; 
		moMarkingMenu = m1.getChild(1);
		
		copyAndFilterMenuData(0, -1, -1, 0);
		
		///========== finish loading the content for this condition =====
		
		clearHysteresis();
		
		// update the menu content, if needed
		copyAndFilterMenuData(condition.getItemsPreShuffling(miParticipantID), condition.getCategorySlice(miParticipantID)[0], condition.getCategorySlice(miParticipantID)[1], condition.getItemsPostShuffling(miParticipantID));
		
		//==> we need to set the angle of attack differently than the others 
		if(conditionToMode(moCurrentCondition) == EARPOD_MODE){
			///the visualmarkingmenu also need to setup the pulldown list
			
			if(miCurrentMenuBreadth == 4){
				mdStartAngle = Math.toDegrees(Math.PI / 4.0 + Math.PI / 2.0);
			}
			else if(miCurrentMenuBreadth == 8){
				mdStartAngle = Math.toDegrees(Math.PI / 8.0 + Math.PI / 2.0);
			}
			else if(miCurrentMenuBreadth == 12){
				mdStartAngle = Math.toDegrees(Math.PI /12.0 + Math.PI / 2.0);
			}
			
		}
		
		//Try to clear a few things
		moSoundSetMap.clear();
		moItemNamesMap.clear();
		
		//there are further things will be divided under resetSound
		//resetSound will be needed for both visual and audio experiment
		//so we keep it outside of the condition list
		resetSound(condition, BeforeFirstAudioCondition && BeforeFirstVisualCondition);
		
/*		if(miCurrentExperimentMode == ExperimentCondition.AudioPulldownMenu){
			//work on the audio pull down menu 
			moVisualFrame.setVisible(false);
			
			if (DEBUG) moDebugFrame.setVisible(true);
			//moNavigator.setVisible(true);
			BeforeFirstAudioCondition = false;
		}
		else */
		if(conditionToMode(moCurrentCondition) == PULLDOWN_MODE ||
				miCurrentExperimentMode == ExperimentCondition.VisualMarkingMenu ||
				miCurrentExperimentMode == ExperimentCondition.AVEarPodMenu
		){ //we know it's in the visual menu mode
			
			//set up the menu 
			//SetupVisualMenu(condition);
			if(BeforeFirstVisualMarkingCondition || 
					BeforeFirstVisualPulldownCondition || 
					!moCurrentCondition.USE_SINGLE_CONTENT_FILE){
				//String[] items = (String[]) moVisualElementMap.get("0");
				String[] items = {""};//, "Welcome to our experiment!"," ", "Tap the touchpad to start the trials!"};
				
				JScrollPane scroll = null; 
				
				///set up the different conditions here
				if(miCurrentExperimentMode == ExperimentCondition.VisualMarkingMenu ||
						miCurrentExperimentMode == ExperimentCondition.AVEarPodMenu)
				{
					moVisualMarkingPanel = new VisualMarkingPanel();
					moVisualMarkingPanel.setMenuItems(items);
					moVisualMarkingPanel.setFont(IPOD_FONT);
					moVisualMarkingPanel.setPreferredSize(VF_WIDTH*2,VF_HEIGHT);
					//scroll = new JScrollPane(moVisualMarkingPanel);
				}//end of visual marking
				else{
					moVisualList = new JList(items);
					///need to set up the icons view for the visual menu
					//if(miCurrentExperimentMode == ExperimentCondition.VisualMarkingMenu)
					//	moVisualList.setCellRenderer(new LabelCellRenderer());
					
					moVisualList.setFont(IPOD_FONT);
					//moVisualList.setSelectedIndex(0);
					moVisualList.setAutoscrolls(true);
	
					scroll = new JScrollPane(moVisualList);
				}//end of pull down interface
				
				iPodLabel = new JLabel("", null, JLabel.LEFT);
				iPodLabel.setFont(IPOD_FONT);
				iPodLabel.setBackground(Color.GRAY);
				//iPodLabel.setHorizontalTextPosition(JLabel.CENTER);
				moVisualFrame.getContentPane().add(iPodLabel, BorderLayout.NORTH);
				if(miCurrentExperimentMode == ExperimentCondition.VisualMarkingMenu ||
						miCurrentExperimentMode == ExperimentCondition.AVEarPodMenu)
				{
					moVisualFrame.getContentPane().add(moVisualMarkingPanel, BorderLayout.CENTER);
					BeforeFirstVisualMarkingCondition = true;
				}
				else{
					moVisualFrame.getContentPane().add(scroll, BorderLayout.CENTER);
					BeforeFirstVisualPulldownCondition = true;
				}
				
				//moVisualFrame.setSize(new Dimension(250, 320));
				moVisualFrame.setSize(new Dimension(VF_WIDTH, VF_HEIGHT));
				Dimension screenSize = java.awt.Toolkit.getDefaultToolkit().getScreenSize();
				moVisualFrame.setLocation((screenSize.width-moVisualFrame.getWidth())/2, (int)((screenSize.height-moVisualFrame.getHeight())/1.5f));  
			}//end of if(BeforeFirstVisualCondition){
			
			

		}//end of if(miCurrentExperimentMode == ExperimentCondition.VisualPulldownMenu ||
	

		if (DEBUG) {
			moDebugFrame.setVisible(true);
			moDebugFrame.toFront();
		}
		clearVisualMessage(1);
	}//end of method
	
	public void increaseTrialIndex(int amount){
		miTrialIndex += amount;
	}
	
	public void increaseConditionIndex(int amount){
		miConditionIndex += amount;
	}
	
	/**
	 * Need this method to get the current experiment mode 
	 * @return
	 */
	public int getCurrentExperimentMode(){
		return miCurrentExperimentMode;
	}
	
	void EndTrial(boolean tooSoon){

		setExperimentStatus(TRIAL_FINISHED);
		
	    if (conditionToMode(moCurrentCondition) == PULLDOWN_MODE) {	    	
	    	mlFinishDrawing = Logger.getTime();
			//time to calculate the reaction time and execution time
			if (moCurrentCondition.AUTOMATIC_STIMULI)
				mlReactionTime = mlStartDrawing - mlStartStimulus;
			else
				mlReactionTime = mlStartDrawing - mlFinishStimulus;
			mlExecutionTime = mlFinishDrawing - mlStartDrawing;
	    	debug("REACTION=" + mlReactionTime + " EXECUTION=" + mlExecutionTime);
	    }

		if (!tooSoon)
			HighLevelLog("END_TRIAL");
		// flush all logged data (in any case)
		saveData(null);

		boolean hit = CompareTwoStringArray(moCurrentStimulus.maStimuli, maCurrentResponse.toArray());
		
		setExperimentStatus(BEFORE_TRIAL);
		
		///////////////////////////////////////////////////////////////////////////////
		// PLAY RESPONSES	
		///////////////////////////////////////////////////////////////////////////////
		
		if (!tooSoon) {	
			try{
				sleep(200);
			}catch(Exception e){}
			
			//display the message on the screen 
			//To work on the flow, I will first display
		    if (conditionToMode(moCurrentCondition) == PULLDOWN_MODE
					) {	    	
		    	moVisualList.setListData(new String[] {});
		    }
		    else if(miCurrentExperimentMode == ExperimentCondition.VisualMarkingMenu ||
					miCurrentExperimentMode == ExperimentCondition.AVEarPodMenu)
		    {
		    	moVisualMarkingPanel.setMenuItems(new String[] {});
		    }//end of else if 
		    
			if(hit){
				// -- ==> RIGHT --
		        moProgress.setCurrentTrial(miTrialIndex);
		        if (moCurrentCondition.AUTOMATIC_STIMULI) {
		        	// if automatic stimuli, don't answer anything
					if (moCurrentCondition.VISUAL_STIMULI) {
						clearVisualMessage(0);
					}
					// just pause a little bit
		        	sleep(300);
		        } else {
					if (moCurrentCondition.VISUAL_STIMULI && !moCurrentCondition.AUTOMATIC_STIMULI) {
						setVisualMessage("Right", 0, Color.white);
					}
					if (moCurrentCondition.AUDIO_STIMULI) {
						ResponseSounds.play(0);
						WaitToFinish(ResponseSounds, 0);
					} else {
						sleep(1000);
					}
					if (moCurrentCondition.VISUAL_STIMULI) {
						clearVisualMessage(0);
					}
		        }
			} else {
				// -- ==> MISMATCH --
				if (moCurrentCondition.VISUAL_STIMULI) {
					setVisualMessage("Mismatch", 0, Color.red);
				}
				if (moCurrentCondition.AUDIO_STIMULI) {
					ResponseSounds.play(1);
					WaitToFinish(ResponseSounds, 1);
				} else {
					sleep(2000);
				}
				if (moCurrentCondition.VISUAL_STIMULI) {
					clearVisualMessage(0);
				}				
			}
			repaint();
			
		} else {
			// -- ==> TOO SOON --
			if (moCurrentCondition.VISUAL_STIMULI) {
				clearVisualMessage(0);
			}
			UISounds2.play(6);
			WaitToFinish(UISounds2, 6);
		}
		
		//////////////////////////////////
				
		if(miTrialIndex == moCurrentInstructionSet.size()){
			if(!DEMO_MODE)
				EndCondition();
			else{
				//go back to the beginning of the loop 
				miTrialIndex = 0;
			}
		} else {
			maCurrentResponse.clear();
			mlStartDrawing = -1;
			mlFinishDrawing = -1;
			mlReactionTime = -1;
			mlExecutionTime = -1;
			mlFinishStimulus = -1;
			mlStartStimulus = -1;
			mfLastZoneIndex = -100F; // make sure it's different from others
			//in order to prevent the experimenter from rushing through the experiment
			//if they made a mistake, we require them to do it again
			if(hit)
				miTrialIndex ++;
		}
		
		//////
		if(miTrialIndex == moCurrentInstructionSet.size()){
			if(!DEMO_MODE){
				EndCondition();
				return;
			}
			else 
				miTrialIndex = 0;
			
		}

		// Brief pause?
		if (hit && moCurrentCondition.pauseAfter(miParticipantID, miTrialIndex)) {
			// pause between blocks
			moProgress.setVisible(moCurrentCondition.SHOW_PROGRESS);
			if(!DEMO_MODE)
				WaitForKeyPress("Press space bar to continue");
			moProgress.setVisible(false);
			sleep(500);
		}
		
		// Start next stimulus?
		if (moCurrentCondition.AUTOMATIC_STIMULI) {
			handleStartStimulusEvent();
		}
	}
	
	public void EndCondition() {
		EndCondition(miConditionIndex+1, null);
	}
	
	public void EndCondition(int nextCondition, String logMessage) {

		moNavigator.setEnabled(false);		
		// flush all logged data
		saveData(logMessage);
		
		boolean BREAK = moCurrentCondition.breakAfter(miParticipantID);
		
		miConditionIndex = nextCondition-1;
		
		moProgress.setVisible(moCurrentCondition.SHOW_PROGRESS);

		// skip conditions with no trial
		do {
			miConditionIndex++;
			//reset condition related variables
			if (miConditionIndex < moConditionList.size()) {
				moCurrentCondition = (ExperimentCondition) moConditionList.get(miConditionIndex);
				moCurrentInstructionSet = (ArrayList) moExperimentSet.get(moCurrentCondition.toString());
			}
		} while(moCurrentInstructionSet.size()==0 && miConditionIndex < moConditionList.size());
		
		if(miConditionIndex >= moConditionList.size()){
			moProgress.endOfExperiment();
			if (moCurrentCondition.VISUAL_STIMULI) {
				setVisualMessage("End of session", 0, Color.white);
				setVisualMessage("Ask the experimenter for instructions", 1, Color.white);
				sleep(2500);
			}
			if (moCurrentCondition.AUDIO_STIMULI) {
				CommandSounds.play(3); // end of experiment
				WaitToFinish(CommandSounds, 3);
				CommandSounds.play(8); // ask instruction
				WaitToFinish(CommandSounds, 8);
			} else {
				
			}
			EndExperiment();
			return;
		} 
	
	    if ((conditionToMode(moCurrentCondition) == PULLDOWN_MODE)
	    		&& moVisualList != null) {
			moVisualList.setListData(new String[] {});
	    }
	    

		if (moCurrentCondition.VISUAL_STIMULI) {
			setVisualMessage("End of condition", 0, Color.white);
		}
		if (moCurrentCondition.AUDIO_STIMULI) {
			CommandSounds.play(4); //which plays the end of condition command
			WaitToFinish(CommandSounds, 4);
		} else {
			sleep(1000);
		}
		
		moVisualFrame.setVisible(false);

	    int sectionnumber = ((ExperimentCondition) moConditionList.get(miConditionIndex)).getInstructionSection(miParticipantID);

	    // break?
	    if (BREAK) {
		    if (moCurrentCondition.VISUAL_STIMULI) {
				setVisualMessage("Please take a 5mn break", 0, Color.white);
		    }
			if (moCurrentCondition.AUDIO_STIMULI) {
				CommandSounds.play(5); //which plays the take a break command
				WaitToFinish(CommandSounds, 5);
			} else {
				sleep(1000);
			}
		    
		    // If there is a section to read, pause now (user will read it after the break)
		    if (sectionnumber > 0 && !DEMO_MODE)
		    	WaitForKeyPress("Then, press space bar to continue");
	    }
	    
	    // tell the section to read?
	    if (sectionnumber > 0) {
	    	if (moCurrentCondition.VISUAL_STIMULI && !DEMO_MODE) {
	    		String msg = "Read section " + sectionnumber + " on the instruction sheet";
	    		setVisualMessage(msg, 0, Color.white);
	    	}
	    	if (moCurrentCondition.AUDIO_STIMULI) {
			    CommandSounds.play(9); //which plays the "read the section"
			    WaitToFinish(CommandSounds, 9);
			    long duration = PlayNumber(sectionnumber);
			    try{
					sleep(duration);
				}catch(Exception e){}
				CommandSounds.play(7); //which plays the "on instruction sheet"
			    WaitToFinish(CommandSounds, 7);
	    	}
	    }
		    
		//reset trial related variables 
		miTrialIndex = 0;
		setExperimentStatus(BEFORE_TRIAL);

		//before start the condition, I need to clear the list
	    moGraphicModel = null;
	    
	    SetExperimentUnderCondition(moCurrentCondition);

	    // wait for key press to give the time to read the instructions
	    if(!DEMO_MODE)
	    	WaitForKeyPress("Press space bar to start");
	    
	    moProgress.setVisible(false);
	    
	    if (moCurrentCondition.miTypeOfMenu == ExperimentCondition.VisualPulldownMenu||
	    		moCurrentCondition.miTypeOfMenu == ExperimentCondition.VisualMarkingMenu ||
	    		moCurrentCondition.miTypeOfMenu == ExperimentCondition.AVEarPodMenu ||
	    		moCurrentCondition.miTypeOfMenu == ExperimentCondition.AVPulldownMenu) {
			moVisualFrame.setVisible(true);
			moVisualFrame.toFront();
	    }
	    
	    if (moCurrentCondition.AUTOMATIC_STIMULI) {
			clearVisualMessage(0);
			sleep(1000);
			handleStartStimulusEvent();
	    } else {
		    if (moCurrentCondition.VISUAL_STIMULI) {
				setVisualMessage("Press the touchpad center to start", 0, Color.white);
		    }
	//	    onScreenMessage(moCurrentCondition.toString());
		    if (moCurrentCondition.AUDIO_STIMULI) {
			    CommandSounds.play(1); //which plays the start of condition command
			    WaitToFinish(CommandSounds, 1);
			    if (!moCurrentCondition.AUTOMATIC_STIMULI) {
			    	CommandSounds.play(2); //  touch the touchpad
			    	WaitToFinish(CommandSounds, 2);
			    }
		    } else {
		    	sleep(1000);
		    }
			moNavigator.setEnabled(true);
	    }
	}
	
	public void WaitToFinish(SoundSet set, int index) {	
		long duration = (long) (set.getDuration(index) * 1000);
	    try{
	    	sleep(duration);
	    }catch(Exception e){}
	}
	
	private void WaitForTouchpadKeyPress() {
		setVisualMessage("Press side button to continue", 1, Color.white);
		waitForKeyPress = true;
		boolean disable = moNavigator.isEnabled();
		if (disable) moNavigator.setEnabled(false);
		// if already pressed, wait for release
		while (_button > 0) {
			pollingTask.run();
			try {Thread.sleep(100);} catch (Exception e) {}
		}
		// wait for press
		while (_button == 0) {
			pollingTask.run();
			try {Thread.sleep(100);} catch (Exception e) {}
		}
		waitForKeyPress = false;
		clearVisualMessage(1);
		if (disable) moNavigator.setEnabled(true);
	}
	
	private void WaitForKeyPress(String message) {
		setVisualMessage(message, 1, Color.white);
		boolean disable = moNavigator.isEnabled();
		if (disable) moNavigator.setEnabled(false);
		
		spacebarPressed = false;
		waitForKeyPress = true;
		while (!spacebarPressed) {
			try {Thread.sleep(100);} catch (Exception e) {}
		}
		waitForKeyPress = false;
		spacebarPressed = false;
		clearVisualMessage(1);
		if (disable) moNavigator.setEnabled(true);
	}
	
	private void sleep(long ms) {
		// avoid concurrent threads
		boolean disable = moNavigator.isEnabled();
		if (disable) moNavigator.setEnabled(false);
		try {Thread.sleep(ms);} catch (Exception e) {}
		if (disable) moNavigator.setEnabled(true);
	}
	
	
	
	/// <summary>
    /// The method for ending the experiment 
    /// </summary>
    public void EndExperiment()
    {
        //System.Windows.Forms.MessageBox.Show("End Experiment! Participant " + miParticipantID + ", Thank you for your time!");
    	//play end of experiment to the user
    	moDetailedLogger.flushText("END_OF_SESSION");
        moHighLevelLogger.flushText("END_OF_SESSION");
        StereoSample.logger.flushText("END_OF_SESSION");
    	terminateTouchPad();
    	SoundManager.quit();
        System.exit(0);
    }
    
    // it is better to do it often
    public void saveData(String separator) {
    	moDetailedLogger.flushText(separator);
        moHighLevelLogger.flushText(separator);
        StereoSample.logger.flushText(separator);
    }
    
    public void logDetailedEvent(String eventName, String arg) {
    	logDetailedEvent(eventName, arg, -1, -1, -1, -1);
    }
    
    /**
	 * log the detail event for the detailed log, this method 
	 * try to handle all the cases here, so the interface looks 
	 * clean outside
	 * @param eventName
	 * @param x
	 * @param y
	 * @param z
	 * @param button
	 */
	public void logDetailedEvent(String eventName, String arg, int x, int y, int z, int button){
		
		float itemNumber = -1F;
		
		if(conditionToMode(moCurrentCondition) == EARPOD_MODE){
			itemNumber = getZoneIndex(x,y);
			//we don't log the event if the zone is very similar
			//for example they are the same exact zone up to .1 precision
			if(eventName == DRAG_EVENT){
				if((int) (itemNumber * 10) == (int) (mfLastZoneIndex*10)) return;
			}
		}
		else{
			if(moVisualList != null)
				itemNumber = moVisualList.getSelectedIndex();
			
			if(eventName == DRAG_EVENT){
				//check which item it is on	
				if(itemNumber == miLastSelectedVisualIndex) return;
				else miLastSelectedVisualIndex = (int) itemNumber;
			}
		}
		
		DetailedLog(eventName, arg, itemNumber, x, y, z);
	}
    
    private String EssentialHighLevelElements(boolean isDetailedLog)
    {
        String logLine = "";
        // column 2 - participant ID
        logLine += miParticipantID + LOG_CONNECTOR;
        
        // split condition String into its name and its part
        String condition = moCurrentCondition.toString();
        int part = 1;
        int ni = condition.indexOf("#");
        if (ni > 0) {
        	part = Integer.parseInt(condition.substring(ni+1).trim());
        	condition = condition.substring(0, ni-1);
        }
        
        // column 3 - condition name 
        logLine += condition + LOG_CONNECTOR;

        // column 4 - condition part (column was empty before)
        logLine += part + LOG_CONNECTOR;
       
        // column 5 - condition index
        logLine += miConditionIndex + LOG_CONNECTOR;

        // column 6 - block number
        if(moCurrentStimulus != null)
        	logLine += moCurrentStimulus.miBlockNumber + LOG_CONNECTOR;
        else
        	logLine += -1 + LOG_CONNECTOR;

        // column 7 - trial number
        logLine += miTrialIndex + LOG_CONNECTOR;

        // column 8 - ??
        //need to get the stimuliCategory (make sure it is correct)
        if(moCurrentStimulus != null)
        	logLine += moCurrentStimulus.GetCategories() + LOG_CONNECTOR;
        else
        	logLine += "null" + LOG_CONNECTOR;
        
        //this part is for highlevel log only
        if(!isDetailedLog){
	        String instructionStr = ArrayToString(moCurrentStimulus.maStimuli, "_");
	        String responseStr = ArrayToString(maCurrentResponse.toArray(), "_");
	        
	        // column 9 - stimulus
	        logLine += instructionStr + LOG_CONNECTOR;
	        
	        // column 10 - user's response
	        logLine += responseStr + LOG_CONNECTOR;
	        
	        // column 11 - right/mismatch
	        if (CompareTwoStringArray(moCurrentStimulus.maStimuli, maCurrentResponse.toArray()))
	        {
	            logLine += "1 ";
	        }
	        else
	        {
	            logLine += "0 ";
	        }
        }
        else{
        	
        	// column 9 - stimulus
        	if(moCurrentStimulus != null)
        		logLine += ArrayToString(moCurrentStimulus.maStimuli, "_") + LOG_CONNECTOR;
        	else
        		logLine += "no_sti" + LOG_CONNECTOR;
        }

        return logLine;
    }

    public void HighLevelLog(String actionType)
    {
        String logLine = EssentialHighLevelElements(false); //this is highlevel log
        logLine += actionType + LOG_CONNECTOR;

        ///here is the three main lines of things I am caring about
        
        // column 12 - reaction time after stimulus start
        logLine += mlReactionTime + LOG_CONNECTOR;
        
        // column 13 - execution time
        logLine += mlExecutionTime + LOG_CONNECTOR;
        
        // column 14 - total time
        logLine += (mlReactionTime + mlExecutionTime) + LOG_CONNECTOR;

        // column 15 - ???
        logLine += MACHINE_ID + LOG_CONNECTOR;
        moHighLevelLogger.log(actionType, logLine);
    }
    
   
 

	/**
	 * This method construct the sunburst menu
	 * from the treemodel we parsed from xml file
	 * and return to the user
	 * 
	 * The tree model has two nodes:
	 * 1. Sunburst menu
	 * 		Sunburst SubMenu
	 * 		Sunburst Submenu
	 * 		...
	 * 
	 * 2. marking menu
	 * 
	 * @param m1
	 * @return
	 */
	public static SimpleStrokeAudioUIbk getMenuFromTree(TreeModel m1) {

		ArrayList list = new ArrayList();

		for (int i = 0; i < m1.getChild(0).childCount(); i++) {
			list.add(m1.getChild(0).getChild(i));
		}

		SimpleStrokeAudioUIbk menu = new SimpleStrokeAudioUIbk(list, m1.getChild(1));
		return menu;
	}
	
	
	
	public static void main(String[] args) {
        
		if (args.length != 2)  return;
		
		// touchpad parameters
		if (args[0].equals("redmond")) {
			MACHINE_ID = "redmond-touchpad";
			TP_HMAX = REDMOND_TP_HMAX;
			TP_HMIN = REDMOND_TP_HMIN;
			TP_VMAX = REDMOND_TP_VMAX;
			TP_VMIN = REDMOND_TP_VMIN;
		} else if (args[0].equals("toronto")) {
			MACHINE_ID = "toronto-touchpad";
			TP_HMAX = TORONTO_TP_HMAX;
			TP_HMIN = TORONTO_TP_HMIN;
			TP_VMAX = TORONTO_TP_VMAX;
			TP_VMIN = TORONTO_TP_VMIN;
		} else {
			return;
		}
		
		ExperimentCondition experiment = null;
		try {
			experiment = (ExperimentCondition)(Class.forName("experiment.designs." + args[1]).newInstance());
		} catch (Exception e) {
			e.printStackTrace();
			return;
		}
		
		//set the status to initial state first
		setExperimentStatus(BEFORE_EXPERIMENT);
		
		//get the participant id here: 
		msUsrID = G.input("Please enter the user id:");
		
		//msSession = G.input("Please enter your session id:");
		
		// redirect all output to log.
		if (!DEBUG) {
			try {
				PrintStream log = new PrintStream(Logger.getLogFilename("stdout"));
				System.setOut(log);
				System.setErr(log);
			} catch (Exception e) {
				e.printStackTrace();
			}
		}
		
		//miSession = -1;
		miParticipantID = 0;
		
		if(msUsrID == null || msUsrID.equalsIgnoreCase("") ) 
		{	G.message("Need to input your user id and session number! The program now quit.");
			System.exit(0);
			} 
		
		try {
			//miSession = Integer.parseInt(msSession);
			miParticipantID = Integer.parseInt(msUsrID);
		} catch (Exception e) {
			//miSession = 0;
			miParticipantID = 0;
		}
		
		// randomize experiment according to the user ID
		ExperimentBuilder.rand.setSeed(miParticipantID);
		
		//===> initialize the logger information
		moHighLevelLogger = Logger.createLogger("log");
		moDetailedLogger = Logger.createLogger("touchpad_log");

		// background window
		if (!DEBUG) {
			moBackgroundWindow = new JWindow();
			JPanel jp = new JPanel();
			jp.setBackground(new Color(0.1f, 0.2f, 0.5f));
			moBackgroundWindow.getContentPane().add(jp);
			moBackgroundWindow.setBackground(new Color(0.1f, 0.2f, 0.5f));
			moBackgroundWindow.setForeground(new Color(0.1f, 0.2f, 0.5f));
			moBackgroundWindow.setSize(Toolkit.getDefaultToolkit().getScreenSize().width, Toolkit.getDefaultToolkit().getScreenSize().height+50);
			moBackgroundWindow.setLocation(0, 0);
			moBackgroundWindow.setVisible(true);
			moBackgroundWindow.setCursor(Toolkit.getDefaultToolkit().createCustomCursor(new BufferedImage(10,10,BufferedImage.TYPE_INT_ARGB), new Point(0, 0), "NO CURSOR"));
		}
		
		//===> start the JFrame 
		moDebugFrame = new JFrame("Debug");
		//===> initialize visual frames
		moVisualFrame = new JWindow();

		//JPanel panel = new JPanel();
		readOnlyCB.addActionListener(new ComboListener()); 

		//frame.getContentPane().add(moHSlider, BorderLayout.NORTH);
		moDebugFrame.getContentPane().add(readOnlyCB, BorderLayout.NORTH);
		moDebugFrame.getContentPane().add(moLabel, BorderLayout.SOUTH);
		moContentPane = (JPanel) moDebugFrame.getContentPane();

		moDebugFrame.setSize(new Dimension((int)(DEBUG_WIDTH*DEBUG_ZOOM)+10, (int)(DEBUG_HEIGHT*DEBUG_ZOOM)+10));
		moDebugFrame.setLocation(0, 100);

		//==> Here, let me do something with the sunburst loader
		//SaxSunburstLoader ssl = new SaxSunburstLoader();
		//ssl.loadMenus(experiment.getXMLContentFile());
		//final SimpleStrokeAudioUI menu = getMenuFromTree(ssl.getDefaultTreeModel());
		
		final SimpleStrokeAudioUIbk menu = new SimpleStrokeAudioUIbk();
		
		moNavigator = new JExperimentNavigator(menu);

		moContentPane.add(menu, BorderLayout.CENTER);

		ItemListener listener = new ItemListener();
		listener.setAudioUI(menu);
		moDebugFrame.addKeyListener(listener);
		moDebugFrame.setFocusable(true);
		
		moVisualFrame.addKeyListener(listener);
		moVisualFrame.setFocusable(true);
		//moContentPane.setFocusable(true);
		//menu.setFocusable(true);
		
		moDebugFrame.addWindowListener(new WindowAdapter() {
			public void windowClosing(WindowEvent evt) {
				//terminateTouchPad();
				//SoundManager.quit();
				//System.exit(0);
				menu.EndExperiment();
			}
		});
		
		moVisualFrame.addWindowListener(new WindowAdapter() {
			public void windowClosing(WindowEvent evt) {
				//terminateTouchPad();
				//SoundManager.quit();
				//System.exit(0);
				menu.EndExperiment();
			}
		});
		
		if (ALLOW_SPACEBAR) {
			// modify awt event queue to listen to keyboard events
			java.awt.Toolkit.getDefaultToolkit().getSystemEventQueue().push(
				new EventQueue() {
				  protected void dispatchEvent(AWTEvent event) {
					super.dispatchEvent(event);
				    if (event instanceof KeyEvent) {
				    	KeyEvent ke =(KeyEvent)event; 
				    	if (ke.getKeyCode() == KeyEvent.VK_SPACE) {
				    		if (ke.getID() == KeyEvent.KEY_PRESSED) {
				    			menu.spacebarPressed();
				    		}
						}
				    }
				  }
				});
		}
		
		//start the experiment 
		try {
			menu.StartExperiment(experiment);
		} catch (Exception e) {
			e.printStackTrace();
			System.exit(0);
		}
	
		//moDebugFrame.setVisible(true);
		//moVisualFrame.setVisible(true);
		
		//===================================================================
		//Testing code 
		//===================================================================
		
		/*System.out.println("output is " + SimpleStrokeAudioUI.getAngleChange(-135, -1, -135,1));
		System.out.println("output is " + SimpleStrokeAudioUI.getAngleChange(1, 109, -1, 109));
		System.out.println("output is " + SimpleStrokeAudioUI.getAngleInDegrees(-14, 0));
		System.out.println("output is " + SimpleStrokeAudioUI.getAngleInDegrees(14, 0));
		System.out.println("output is " + SimpleStrokeAudioUI.getAngleInDegrees(-14, 0));*/
	}

	/** 
	 * Adds a prefix and a suffix to the filename and create a Logger
	 * @param file
	 * @return
	 */

	
	//////////////////////////////////////////////////////////////////////////////
	//////////////////////////////////////////////////////////////////////////////
	//////////////////////////////////////////////////////////////////////////////
	//////////////////////////////////////////////////////////////////////////////
	//TODO: end of main method
	//////////////////////////////////////////////////////////////////////////////
	//////////////////////////////////////////////////////////////////////////////
	//////////////////////////////////////////////////////////////////////////////
	//////////////////////////////////////////////////////////////////////////////
	
	 /**
     * 
     * @param actionType: how many action types: fingerTouched, fingerReleased, fingerDragged, 
     * I don't want to log all the drag event, since it might be too much. I only log 
     * the event where the zone of the finger is different from the last event.
     * @param zoneNumber
     */
    /**
     * @param actionType
     * @param experimentStatus
     * @param zoneNumber
     * @param x
     * @param y
     * @param z
     */
    private void DetailedLog(String actionType, String arg,  float zoneNumber, int x, int y, int z)
    {
        //throw new Exception("The method or operation is not implemented.");
        String logLine = EssentialHighLevelElements(true); //this is detailed log
        
        String status = statusToString(ExperimentStatus);
        
        // - 
        
        logLine += actionType + LOG_CONNECTOR;
        logLine += arg + LOG_CONNECTOR;
        logLine += status + LOG_CONNECTOR;
        
        logLine += "zone_"+zoneNumber + LOG_CONNECTOR;
        logLine += x +","+y+","+z + LOG_CONNECTOR;

        logLine += MACHINE_ID + LOG_CONNECTOR;
        moDetailedLogger.log("", logLine);
    }
    
	/**
	 * get the zoneIndex for the current x and y position
	 * @return
	 */
	private float getZoneIndex(int x, int y){
		int numOfItemsThisLevel = 0;
		if(moLastItem == null){
			//we are at the root
			numOfItemsThisLevel = moGraphicModel.getLayer(0).size();
		}
		else{
			//check the leaf level 
			numOfItemsThisLevel = moLastItem.getChildCount();
		}
		
		float zoneIndex = getWeightedZoneN(x, y, mdStartAngle,
				numOfItemsThisLevel, null, 0);
		
		return zoneIndex;
	}
	
	private static void onScreenMessage(String message) {
		moLabel.setText(message);
	}
	
	//Dependency
	//        playStimulus 
	//			--->playStimuliItem<---
	//				selectStimuliItem
	
	/**
	 * play an item for the stimuli, since we require it to play one after the other
	 * we use playWhenEmpty instead
	 * @param item the menu item in focus
	 * @param setLastItem if it's true, that means we are selecting it, otherwise, we are only previewing it
	 */
/*	private float playStimuliItem(AudibleMenuItem item, int zoneIndex, boolean setLastItem ){
		SoundSet sSet = null;
		if(item != null)
			sSet = (SoundSet) moStimulusSoundMap.get(item.getID());
		else
			sSet = (SoundSet) moStimulusSoundMap.get("0");
		
		sSet.playWhenEmpty(zoneIndex);
		return sSet.getDuration(zoneIndex);
	}
	*/
	//	Dependency
	//        playStimulus 
	//				playStimuliItem
	//			--->selectStimuliItem<---
	
	/**
	 * Return the mode for specific experiment condition
	 * 	final public static short VisualPulldownMenu = 12; // **
	final public static short AudioPulldownMenu = 13; // 
	final public static short VisualMarkingMenu = 14;
	final public static short AVEarPodMenu = 15; //this is the combined technique, with both audio and visual stimuli
	final public static short AVPulldownMenu = 16;
	
	///the two menus we need to do right now are the audio and visual long list menu
	final public static short AudioSpiralLongMenu = 17; // 
	final public static short VisualSpiralLongMenu = 18;
	
	///we want to investigate the ortho zoom stuff here
	final public static short AudioOrthoZoomLongMenu = 19; // 
	final public static short VisualOrthoZoomLongMenu = 20;
	
	///we want to investigate the ortho zoom stuff here
	final public static short AudioSpeedDependentLongMenu = 21; // 
	final public static short VisualSpeedDependentLongMenu = 22;

	 */
	private short conditionToMode(ExperimentCondition condition){
		switch(condition.miTypeOfMenu){
		case ExperimentCondition.AudioPulldownMenu:
		case ExperimentCondition.VisualPulldownMenu:
		case ExperimentCondition.AVPulldownMenu:
			return PULLDOWN_MODE;
		case ExperimentCondition.EarPodMenu:
		case ExperimentCondition.VisualMarkingMenu:
		case ExperimentCondition.AVEarPodMenu:	
			return EARPOD_MODE;
		case ExperimentCondition.AudioSpiralLongMenu:
		case ExperimentCondition.VisualSpiralLongMenu:
			return SPIRAL_MODE;
		case ExperimentCondition.AudioSpeedDependentLongMenu:
		case ExperimentCondition.VisualSpeedDependentLongMenu:
			return SPEEDZOOM_MODE;
		case ExperimentCondition.AudioOrthoZoomLongMenu:
		case ExperimentCondition.VisualOrthoZoomLongMenu:
			return ORTHOZOOM_MODE;
		}
		return -1;
	}
	
	/**
	 * get the item in focus under the zone. We can either browse 
	 * the item (not selecting it or execute the item. setLastItem
	 * will determine that. If setLastItem is true, that means we 
	 * want to select it, otherwise, we just want to browse it. 
	 * @param itemIndex
	 * @param setLastItem
	 * @return
	 */
	private AudibleMenuItem selectStimuliItem(int itemIndex, AudibleMenuItem parent) {
		AudibleMenuItem item = null;
		if (parent == null) {
			if (itemIndex > -1 && itemIndex < moGraphicModel.getLayer(0).size()) {//moGraphicModel.getLayer(0).size())
				item = (AudibleMenuItem) moGraphicModel.getLayer(0).get(
						itemIndex);				
			}//end if zoneIndex is valid
		} else {
			if (itemIndex > -1 && itemIndex < parent.getChildCount()) {
				item = parent.getChildAt(itemIndex);
			}//end of if(zoneIndex < moLastItem.getChildCount()
		}
		return item;
	}//end of method
	
	/**
	 * transform any index number within a set to a float point number
	 * that can be used in the system to place the sound. For example
	 * index 9 out of 12 total number of directions will corresponding to 
	 * index 6 in 8 directions
	 * @param index 
	 * @param totalNumber
	 * @return
	 */
	private float indexToLocation(int index, int totalNumber){
		return ((float)index/totalNumber)* 8.0F;
	}

	
	   private boolean CompareTwoStringArray(String[] array1, Object[] array2)
	    {
	        if (array1 == null && array2 == null) return true;
	        if (array1 == null && array2 != null) return false;
	        if (array1 != null && array2 == null) return false;
	        if (array1 != null && array2 != null)
	        {
	            if (array2.length != array1.length) return false;
	            else
	            {
	                for (int i = 0; i < array1.length; i++)
	                {
	                    if (!array1[i].equals((String) array2[i]))
	                    {
	                        return false;
	                    }//enf if 1!= 2
	                }//end for
	                return true;
	            }//end else length is equal
	        }//end if both not null
	        return false;
	    }//end method

	    /// <summary>
	    /// provide a way to get String from an array
	    /// </summary>
	    /// <param name="array"></param>
	    /// <param name="connector"></param>
	    /// <returns></returns>
	    private String ArrayToString(String[] array, String connector)
	    {
	        String result = "";
	        if (array == null) return "";
	        for (int i = 0; i < (array.length - 1); i++)
	        {
	            result += array[i] + connector;
	        }
	        result += array[array.length - 1];
	        return result;
	    }
	    
	    //	<summary>
	    /// provide a way to get String from an array
	    /// </summary>
	    /// <param name="array"></param>
	    /// <param name="connector"></param>
	    /// <returns></returns>
	    private String ArrayToString(Object[] array, String connector)
	    {
	        String result = "";
	        if (array == null || array.length == 0) return "";
	        for (int i = 0; i < (array.length - 1); i++)
	        {
	            result += array[i] + connector;
	        }
	        result += array[array.length - 1];
	        return result;
	    }

	   //utility methods 
		
		/**
		 * handle the adding of zone index for the spirals
		 * @param x
		 * @param y
		 * 
		 */
		private void handleSpiralZone(int x, int y){
			int zoneIndex = this.getZoneN(x, y, mdStartAngle, SPIRAL_ITEM_LIMIT);
			if(moZoneList.size() == 0 || zoneIndex != ((Integer)moZoneList.get(moZoneList.size()-1)).intValue()){ 
				moZoneList.add(new Integer(zoneIndex));
			}
		}
		
		/**
		 * provide the name of the file, and return the full name of the file
		 * @param name
		 * @return
		 */
		public static String getFileName(String name) {

			return name + "." + SOUND_SUFFIX;
		}
		
		private static String getPrefix(String string) {
			// TODO Auto-generated method stub
			return "ExperimentAudio/"+string;
		}
		
		public int getTreeDepth() {
			if (moGraphicModel != null)
				return moGraphicModel.getMaxDepth();
			else
				return 0;
		}
		
		/**
		 * Get the radian of the first position. We always start at north.
		 */
		private double getStartRadian() {
			int numItems = getItemCount();

			if (numItems <= 1) {
				return (DEFAULT_START);
			} else {
				double offset = 2 * Math.PI / numItems;
				return (DEFAULT_START - offset / 2);
			}
		} // of method

		public int getItemCount() {
			return (moList.childCount());
		} // of method

/*		public void addItem(TreeModel item) {
			moList.addChild(item);
		}
	*/	
		/**
		 * Add an int value to the speed queue, it is the speed 
		 * of the queue. 
		 * @param speed
		 */
		public void addToSpeedQueue(int speed){
			if(moSpeedQueue.size() < SPEED_QUEUE_SIZE){
				moSpeedQueue.add(new Integer(speed));
			}
			else{
				moSpeedQueue.removeFirst();
				moSpeedQueue.add(new Integer(speed));
			}
		}
		
		/**
		 * return the average speed depending on the speed size
		 *
		 * @return
		 */
		public int getSpeedAverage(){
			if(moSpeedQueue.size() == 0) return 0;
			else{
				int total = 0;
				for(int i=0; i< moSpeedQueue.size(); i++){
					total += ((Integer) moSpeedQueue.get(i)).intValue();
				}
				return total/moSpeedQueue.size();
			}
		}
		
		/**
		 * get current angle in degrees, try to return
		 * the angle between 
		 * @param x
		 * @param y
		 * @return
		 */
		private static double getAngleInDegrees(int x, int y){
			double arcSinValue = y/(Math.sqrt(x*x + y*y));
			double angle = Math.asin(arcSinValue);
			if(x > 0) {
				if(y == 0) return 0.0;
				else if(y > 0) return Math.toDegrees(angle);
				else return Math.toDegrees(Math.PI * 2.0 + angle);
			}
			else if(x < 0) {
				//return Math.toDegrees(angle + Math.PI);
				return Math.toDegrees(Math.PI - angle);
			}
			else if(x == 0){
				if(y > 0) return 90.0; 
				else if(y<0) return 270.0;
				else return 0.0;
			}
			else return 0.0;
		}
		
		/**
		 * get the angle change between two sets of coordinates 
		 * @param x
		 * @param y
		 * @param x1
		 * @param y1
		 * @return
		 */
		private static float getAngleChange(int x, int y, int x1, int y1){
			double angle1 = getAngleInDegrees(x, y);
			double angle2 = getAngleInDegrees(x1, y1);
			double angleChange = angle2 - angle1; 
			
			while(angleChange > 180.0){
				angleChange -= 360.0;
			}
			while(angleChange < -180.0){
				angleChange += 360.0;
			}
			return (float) angleChange;
		}
		
		
		
		/**
		 * if only one up value is taken, use 0 for start index
		 * @param array
		 * @param upTo
		 * @return
		 */
		public int getSumOfIntArray(int[] array, int upTo){
			return getSumOfIntArray(array, 0, upTo);
		}
		
		/**
		 * utility method for getting the sum of an int array
		 * upto the upto index. For example for an array of 
		 * 10 numbers, if upto is 9, then it will only get the
		 * sum of the first 9 numbers
		 * 
		 * 	  N 0 1 
		 * 	.    	2
		 * 	  5 4 3 
		 * 
		 * we assuming this array self connect from the head to toe 
		 * The starting point can be any number, and the end of point can be any number
		 * as well. 
		 * 
		 * @param array
		 * @return
		 */
		public int getSumOfIntArray(int[] array, int startIndex, int upTo){
			int sum = 0;
			
			if(array == null) return sum;
			else {
				//there are several situations
				//we assuming this array is around a circle
				//1. if startIndex <= upTo
				if(upTo > array.length) upTo = array.length;
				if(upTo <0) upTo = 0;
				
				if(startIndex > array.length) startIndex = array.length;
				if(startIndex <0) startIndex = 0;
				
				if(startIndex <= upTo){
					for(int i=startIndex; i< upTo; i++){
						sum+= array[i];
					}//end of for loop
				}
				else if(startIndex > upTo){
					for(int i=startIndex; i< array.length; i++){
						sum+= array[i];
					}//end of for loop
					
					for(int i=0; i< upTo; i++){
						sum+= array[i];
					}//end of for loop	
				}
		
				return sum;
			}//end of else
		}//end of method
		
		/**
		 * Set the slider position for the 
		 * @param value
		 */
		public static void setSliderPosition(int value) {
			//we only allows the values within range 
			if (value < -100 || value > 100)
				return;
			moHSlider.setValue(value);
		}
	
	/**
	 * Takes touch pad points where origin is at the top left corner, and
	 * translate them into screen drawing points.
	 * 
	 * @param touchPadPoint
	 * @return
	 */
	private Point touchPadCornerToScreen(Point touchPadRawPoint) {
		Point screenPoint = new Point();
		//the translation and scaling are 
		double x = touchPadRawPoint.getX() * (DEBUG_RADIUS * 2) / (TP_VMAX - TP_VMIN)
				+ (DEBUG_WIDTH - 2 * DEBUG_RADIUS) / 2; //from the x direction, the real one returns as 1150
		double y = touchPadRawPoint.getY() * (DEBUG_RADIUS * 2) / (TP_HMAX - TP_HMIN)
				+ (DEBUG_HEIGHT - 2 * DEBUG_RADIUS) / 2; //from the y direction, the real one returns as 1040
		screenPoint.setLocation(x, y);
		return screenPoint;
	}

	/**
	 * translate the touch pad points with origin at the top left corner to 
	 * screen points where origin is at the center
	 * @param touchPadRawPoint
	 * @return
	 */
	private Point touchPadCornerToCenter(Point touchPadCorner) {
		double x = touchPadCorner.getX() - (TP_VMAX - TP_VMIN) / 2;
		double y = touchPadCorner.getY() - (TP_HMAX - TP_HMIN) / 2;
		return new Point((int) x, (int) y);
	}

	/**
	 * translate the screen points with origin at the top left corner to 
	 * touchpad points where origin is at the center
	 * @param touchPadRawPoint
	 * @return
	 */
	private Point screenCornerToCenter(Point screenCorner) {
		double x = screenCorner.getX() - (DEBUG_WIDTH - 2 * DEBUG_RADIUS) / 2
				- DEBUG_RADIUS;
		double y = screenCorner.getY() - (DEBUG_HEIGHT - 2 * DEBUG_RADIUS) / 2
				- DEBUG_RADIUS;
		return new Point((int) x, (int) y);
	}

	/**
	 * Given coordinates, starting angle, end angle, and how many divisions
	 * it has, try to get the slice number
	 *
	 * @return the slice number, zero-based, starting at north, going
	 *         counter-clockwise. Returns -1 on error or if the small circle
	 *         contains it.
	 */
	private int _getDetailedSliceNumber(int x, int y, int centerX, int centerY,
			double startRadian, double angleExtent, int numItems) {
		PolarCoordinate polar = _getPolarCoordinates(x, y, centerX, centerY);

		//// 1. Initialize the radian variables.
		double currentRadian = startRadian;

		//we only use 180 degree here because the rest of the 180 degree, we 
		//use it as undo or back to the parent level
		double stepRadian = angleExtent / numItems;
		//double theta = 0.0;
		double theta = 2 * Math.PI + polar.theta;

		//This is a hack that seems to work with 360 degrees or 180 degress
		//but not sure if it will work for arbitrary degrees
		//I haven't find out why this works, but it does seem to work right now
		int balanceFactor = 1;
		if (angleExtent == 2.0 * Math.PI) {
		} else if (angleExtent == Math.PI) {
			balanceFactor = 2;
		} else {
			balanceFactor = (int) ((2 * Math.PI) / angleExtent);
		}

		G.debug("Starting angle is " + Math.toDegrees(startRadian)
				+ " theta is " + Math.toDegrees(polar.theta), 0);

		//The check should be like this: 
		//The start radian range from 0 - 360 degree, and it's 90 degree right (clockwise) to the middle
		//line of the fan, so if theta fall within start - start + 180, it should be good
		//the check will be in two cases: if start is from 0-180, we check with theta > start && theta < start + 180
		// if start is from 180 - 360, we check with theta within start - 360 or start 
		// from 0 - (start + 180) % 360
		if (angleExtent < 2 * Math.PI) {
			//we only interested the cases where we select something out of 
			//a fan instead a whole pie
			if (startRadian < Math.PI) {
				if (polar.theta < startRadian
						|| polar.theta > (startRadian + Math.PI))
					return -1;
			}
			if (startRadian > Math.PI) {
				if (polar.theta > ((startRadian + Math.PI) % (2 * Math.PI))
						&& polar.theta < startRadian) {
					return -1;
				}
			}
		}

		//// 2. Figure out which segment we are in.
		int count = 0;
		for (int i = 0; i < 2 * numItems * balanceFactor; i++) {
			if ((currentRadian < theta)
					&& (theta <= currentRadian + stepRadian)) {
				return (count % numItems);
			}
			count++;
			theta -= stepRadian;
		}

		//// 3. Return -1 for error.
		//over here, error basically means backtrack, but do we need to have
		//a code for error as well? 
		return (-1);
	} // of method

	//Note: I need to implement a set of methods to detect finger change 

	//===========================================================================
	//===   POLAR METHODS   =====================================================

	public static PolarCoordinate _getPolarCoordinates(int x, int y,
			int centerX, int centerY) {
		int xx = x - centerX;
		int yy = y - centerY;

		//// 2. Figure out the radius. Just the distance from (0, 0).
		double distance = Math.sqrt(xx * xx + yy * yy);

		//// 3. Now figure out the angle.
		double normalizedRadius = ((double) xx) / distance;
		double theta = Math.acos(normalizedRadius);

		//// 3.1. Have to dress up theta a little. The function acos() only
		////      returns values from 0.0 to PI. This should mean that
		////      acos() returns the correct value if y is positive.
		////      Normally, this would be < and not >, but screen coordinate
		////      system is backwards.
		if (yy > 0) {
			theta = 2 * Math.PI - theta;
		}

		//// 4. Okay, that's it. Return the answer.
		return (new PolarCoordinate(distance, theta));
	} // of method

	/**
	 * 	This one will return the polar coordinates according to the center
	 * provided
	 * Given an (x,y) coordinate, figure out the angle from the center of this
	 * pie menu, assuming that the origin is at (radius, radius). We can assume
	 * the origin is there since the coordinate space has (0, 0) at the top-left
	 * of this pie menu, meaning that (radius, radius) is the center of this
	 * pie menu.
	 *
	 * <PRE>
	 *                     0 radians
	 *         ---------    |
	 *         |            |
	 *        \/            |
	 *          ------------|--------------
	 *                      |
	 *                      |
	 *                      |
	 *                    pi radians
	 * </PRE>
	 *
	 * @return the angle (in radians) from the positive y-axis,
	 *         going counter-clockwise. Ranges from 0 to less than 2*PI.
	 */
	private PolarCoordinate getPolarCoordinates(int x, int y) {
		// 1. First translate such that x and y are relative to (0, 0).
		int maxDepth = moGraphicModel.getMaxDepth();
		int centerX = (maxDepth * DEBUG_RADIUS+ 2 * DEBUG_RADIUS) / 2;
		int centerY = centerX;
		return _getPolarCoordinates(x, y, centerX, centerY);
	} // of method
	
	/**
	 * Convert polar coordinates to cartesian.
	 *
	 * @param  x       is the x-coordinate of the origin.
	 * @param  y       is the y-coordinate of the origin.
	 * @param  radians is the number of radians to go through.
	 * @param  radius  is the length of the radius.
	 * @param  pt      is the point to put the results in.
	 * @return an (x,y) point.
	 */
	Point polarToCartesian(int x, int y, double radian, double radius, Point pt) {

		//// 1. Convert the polar coordinates.
		////    Normally, the y calculation would be addition instead of
		////    subtraction, but we are dealing with screen coordinates,
		////    which has its y-coordinate at the top and not the bottom.
		pt.x = (int) (x + radius * Math.cos(radian));
		pt.y = (int) (y - radius * Math.sin(radian));

		//// 2. Straighten up the data a little.
		if (Math.abs(pt.x - radius) <= 1) {
			pt.x = (int) radius;
		}
		if (Math.abs(pt.y - radius) <= 1) {
			pt.y = (int) radius;
		}

		return (pt);
	} // of method
	
	/**
	 * These methods are not really used right now. 
	 * @param _x2
	 * @param _y2
	 * @param _button2
	 * @param _z2
	 */
	protected void sliderReleased(int _x2, int _y2, int _button2, int _z2) {
		//debug("sliderReleased");
		
	}
	
	/**
	 * @param _x2
	 * @param _y2
	 * @param _button2
	 * @param _z2
	 */
	protected void sliderDragged(int _x2, int _y2, int _button2, int _z2) {
		//debug("x is " + _x2 + " y is "+ _y2);
		//if(CurrentMode == COMPACT_MODE)
		//	mfLastZoneIndex = computeAndHandleSliderForCurrentItem(_x2, _y2, false, SPIRAL_ITEM_LIMIT); //item selected is false here
	}
	
	/**
	 * @param _x2
	 * @param _y2
	 * @param _button2
	 * @param _z2
	 */
	protected void sliderTouched(int _x2, int _y2, int _button2, int _z2) {
		//debug("sliderTouched");
		
	}
	
	/**
	 * Handle the continuous stroke selection 
	 * The saving of lastZoneIndex will be done outside
	 * 
	 * @param zoneIndex
	 */
	public void handleContinuousStrokeSelection(int zoneIndex, int lastZoneIndex){
		boolean setLastItem = false;
		if(CurrentMode == CONTINUOUS_STROKE_MODE){
			if(CurrentStatus == C_INIT_COMMAND){
				if (zoneIndex == -2) {
					CurrentStatus = C_START_SELECTION;
					mbBrowsingInContinuousStrokeSelection = false;
					UISounds.play(0);
				}//end of zoneIndex == 8
			}//end of CurrentStatus is init command
			else if(CurrentStatus == C_START_SELECTION){
				if(zoneIndex == -2 && mbBrowsingInContinuousStrokeSelection==true){
					//We know the user has selected something
					setLastItem = true;
					playItem(moLastItem, miLastValidZoneIndex, setLastItem);
					AudibleMenuItem item = selectItem(miLastValidZoneIndex, setLastItem);
					
					mbBrowsingInContinuousStrokeSelection = false;
					if(item.getChildCount() == 0){
						this.playEndSelection();
						CurrentStatus = C_INIT_COMMAND;
					}
				}//end of if (center reached)
				else if(zoneIndex > -1 && zoneIndex != lastZoneIndex){
					//play the zone item 
					setLastItem = false;
					mbBrowsingInContinuousStrokeSelection = true;
					selectItem(zoneIndex, setLastItem);
					playItem(moLastItem, zoneIndex, setLastItem);
				}
			}
		}//end of current mode is continuous stroke
	}//end of method
	
	/**
	 * Handling the slider movements. Not used for the circular layout
	 * 
	 * @param x
	 * @param y
	 * @param itemSelected
	 * @param itemLimit
	 * @return
	 */
	public float computeAndHandleSliderForCurrentItem(int x, int y, boolean itemSelected, int itemLimit){
		
		//4. get the zone information 
		int numOfItemsThisLevel = 0;
		if(moLastItem == null){
			//we are at the root
			numOfItemsThisLevel = moGraphicModel.getLayer(0).size();
		}
		else{
			//check the leaf level 
			numOfItemsThisLevel = moLastItem.getChildCount();
		}
		
		//5. get the zone index for 
		float zoneIndex = 0F;
		
		
		//if it is spiral mode, but number of item is less than 8,
		//we use the compact mode
		if (CurrentMode == COMPACT_MODE ) {
			

			//I only worry about x here 
			
			
			float min= 50.0F;
			float max = 1350.0F;
			float totalLength = max- min;
			
			zoneIndex = ((y-min)/totalLength) * numOfItemsThisLevel;
			
			//the following statements using continuous span to circling around the space
			//if(zoneIndex < 0F) zoneIndex += numOfItemsThisLevel;
			//zoneIndex = zoneIndex%numOfItemsThisLevel;
			
			//instead of continuous, I will try limited span here
			
			handleBrowsingCommands(Math.round(zoneIndex), itemSelected);
		
		}
		
		//7. show the debug information of the zone on the screen 
		onScreenMessage("Zone " + zoneIndex);
		
		return zoneIndex;
	}
	
	/**
	 * This is the old handlebrowsingCommands method. 
	 * @param zoneIndex
	 * @param setLastItem
	 */
	/*private void handleBrowsingCommands(int zoneIndex, boolean setLastItem) {
		if (CurrentMode == SPIRAL_MODE || CurrentMode == COMPACT_MODE) {
			switch (CurrentStatus) {
			case C_INIT_COMMAND:
				if (zoneIndex == -2) {
					CurrentStatus = C_START_SELECTION;
					UISounds.play(0);
				}//end of zoneIndex == 8
				break;
			case C_START_SELECTION:
				if (zoneIndex > -1 && zoneIndex != Math.round(mfLastZoneIndex)
						|| setLastItem == true) {

					playItem(moLastItem, zoneIndex, setLastItem);
					AudibleMenuItem item = selectItem(zoneIndex, setLastItem);
					
					if(item == null) return;
					//finally check to see if it's a leaf node or not
					if(item.getChildCount() == 0){
						//we know it's a leaf node 
						if (setLastItem == true) {
							playEndSelection();
							moLastItem = null;
							CurrentStatus = C_INIT_COMMAND;
						}//end of if(setLastItem is true)
						return;
					}//end of checking the leaf node 
				}//end of handling if it's a valid zone or not
				break;
			case C_BETWEEN_SELECTION:
				break;
			}//end of switch 
		}//end of if
	}//end of at the initStatus
	*/
	
	public static void debug(String s) {
		if (DEBUG) {
			System.out.println(s);
		}
	}

	//	dependency
	//		PlayNumber(int)
	//			--->translateNoToVoice(int)<---
	//				PlayNumber(int[] sequence)
	
	private int[] TranslateNoToVoice(int no){
		int[] numbers = null;
		if((no <= 20 || no%10 == 0) && no <= 100){
			//numbers = new String[1];
			//numbers[0] = (String) NUMBER_TO_STRING.get(no+"");
			numbers = new int[1];
			int index = Arrays.binarySearch(NUMBER_POSITIONS, no);
			numbers[0] = index;
		}
		else if(no <= 99 && no > 20){
			numbers = new int[2];
			
			for(int i=0; i<2; i++){
				if(i == 0){
					int no1 = (no/10) * 10;
					int index1 = Arrays.binarySearch(NUMBER_POSITIONS, no1);
					numbers[i] = index1;
				}//end of if(i == 0)
				else if(i == 1){
					int no2 = no%10;
					int index2 = Arrays.binarySearch(NUMBER_POSITIONS, no2);
					numbers[i] = index2;
				}
			}
		}

		return numbers;
	}
	
	//	dependency
	//		PlayNumber(int)
	//				translateNoToVoice(int)
	//			--->PlayNumber(int[] sequence)<---
	
	/**
	 * Play number only plays number >=0 && <= 100
	 * @param positions
	 */
	private long PlayNumber(int[] positions){
		if(positions.length < 1 || positions.length > 2) return 0;
		float timeSpan = 0f;
		if(positions.length == 1){
			NumberSounds.play(positions[0]);
			timeSpan = NumberSounds.getDuration(positions[0]);
		}
		else if(positions.length == 2){
			NumberSounds.play(positions[0]);
			NumberSounds.playAfter(positions[1], positions[0]);
			timeSpan = NumberSounds.getDuration(positions[0]) +  NumberSounds.getDuration(positions[1]);
		}
		
		return (long) (timeSpan * 1000);
	}
	
	/**
	 * private method to adjust the mode of the touchpad interaction techniques. 
	 * Probably won't be used much in the experiment, I don't want to worry about it
	 * 
	 * @author abc
	 */
	private static class ComboListener implements ActionListener {
	 	 public void actionPerformed(ActionEvent evt) {
           JComboBox box = (JComboBox) evt.getSource();
           String mode = (String)box.getSelectedItem();
           if(mode.equals("Spiral Mode")){
           	CurrentMode = SPIRAL_MODE;
           }else if(mode.equals("Compact Mode")){
           	CurrentMode = COMPACT_MODE;
           }else if(mode.equals("Multistroke Marking Mode")){
           	CurrentMode = MULTI_STROKE_MODE;
           }
           else if(mode.equals("Continuous Marking Mode")){
           	CurrentMode = CONTINUOUS_STROKE_MODE;
           }
           
           moLastItem = null;
           CurrentStatus = C_INIT_COMMAND;
       }
   }
	
	static Color addLuminosity(Color c, float amount) {
		float r = c.getRed()/255f + amount;
		float g = c.getGreen()/255f + amount;
		float b = c.getBlue()/255f + amount;
		if (r<0) r=0; if (r>1) r = 1;
		if (g<0) g=0; if (g>1) g = 1;
		if (b<0) b=0; if (b>1) b = 1;
		return new Color(r, g, b);
	}
	
	static Color mix(Color c1, Color c2, float amount) {
		float t2 = amount;
		float t1 = 1-amount;
		float r = (t1*c1.getRed() + t2*c2.getRed()) / 255f;
		float g = (t1*c1.getGreen() + t2*c2.getGreen()) / 255f;
		float b = (t1*c1.getBlue() + t2*c2.getBlue()) / 255f;
		return new Color(r, g, b);
	}	
	
	void setVisualMessage(String message, int line, Color color){
		setVisualMessage(message, line, color, TextPosition.CENTER);
	}
	
	void setVisualMessage(String message, int line, Color color, TextPosition pos) {
		JLabel label;
		if (msgframes[line] == null) {
			msgframes[line] = new JWindow();
			label = new JLabel();
			label.setFont(new Font("Helvetica", Font.BOLD, 30));
			msgframes[line].getContentPane().add(label);
			msgframes[line].setAlwaysOnTop(true);
		} else {
			label = (JLabel)msgframes[line].getContentPane().getComponents()[0];
		}
		msgframes[line].setVisible(false);
		label.setText("  " + message + "  ");
		float r = color.getRed()/255f;
		float g = color.getGreen()/255f;
		float b = color.getBlue()/255f;
		Color winbg = new Color(0.1f, 0.2f, 0.5f);
		Color bg = addLuminosity(mix(color, winbg, 0.6f), 0.5f);
		Color fg = addLuminosity(mix(color, winbg, 0.3f), (color == Color.white)?-0.8f:-0.3f);
		msgframes[line].setBackground(bg);//
		msgframes[line].getContentPane().setBackground(bg);//
		label.setForeground(fg);
		// for some reason, we must wait here for pack() to work well
		sleep(50);
		msgframes[line].pack();
		Dimension screenSize = java.awt.Toolkit.getDefaultToolkit().getScreenSize();
		switch(pos){
		case CENTER:
			msgframes[line].setLocation((screenSize.width-msgframes[line].getWidth())/2, (screenSize.height-msgframes[line].getHeight())/3 + (int)(msgframes[line].getHeight()*1.2*line));
			break;
		case EAST:
			break;
		case WEST:
			break;
		case NORTH:
			msgframes[line].setLocation((screenSize.width-msgframes[line].getWidth())/2, (screenSize.height-msgframes[line].getHeight())/20 + (int)(msgframes[line].getHeight()*1.2*line));
			break;
		case SOUTH:
			break;
		default:
			break;
		}
		
		msgframes[line].setVisible(true);
	}
	
	void clearVisualMessage(int line) {
		if (msgframes[line] != null)
			msgframes[line].setVisible(false);
//		waitframe = null;
	}
	
	
	protected static void setExperimentStatus(short status) {
		ExperimentStatus = status;
		debug("****** STATUS " + status + " " + statusToString(status));
	}
	
	public static String statusToString(short intstatus) {
		String status = "";
        switch(intstatus){
        case BEFORE_EXPERIMENT:
        	status = "before_experiment";
        	break;
        case BEFORE_TRIAL:
        	status = "before_trial";
        	break;
        case USER_TAPPED:
        	status = "user_tapped";
        	break;
        case TRIAL_STIMULUS:
        	status = "trial_stimulus";
        	break;
        case TRIAL_START:
        	status = "trial_start";
        	break;
        case TRIAL_START_REACTED:
        	status = "trial_start_reacted";
        	break;
        case TRIAL_START_TRIGGERED:
        	status = "trial_start_triggered";
        	break;
        case TRIAL_FINISHED:
        	status = "trial_finished";
        	break;
        }
        return status;
	}

}
